mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Align prod schema and deploy tooling
This commit is contained in:
1 parent
d2db86ec7f
commit
7e2e723880
105 files changed
+19014
-131
No files matched your search
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# Export questions from production and generate SQL for non-prod
|
||||
# Usage: ./scripts/export-questions-from-prod.sh > apps/supabase/seed-questions.sql
|
||||
|
||||
# Production database connection string
|
||||
# Get this from Supabase Dashboard > Settings > Database > Connection string
|
||||
PROD_DB_URL="${PROD_DATABASE_URL:-}"
|
||||
|
||||
if [ -z "$PROD_DB_URL" ]; then
|
||||
echo "Error: PROD_DATABASE_URL environment variable is not set" >&2
|
||||
echo "Get it from: Supabase Dashboard > Settings > Database > Connection string (URI)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "-- Questions exported from production"
|
||||
echo "-- Generated on $(date)"
|
||||
echo ""
|
||||
echo "BEGIN;"
|
||||
echo ""
|
||||
|
||||
# Export questions as SQL INSERT statements
|
||||
psql "$PROD_DB_URL" -t -A -c "
|
||||
SELECT 'INSERT INTO questions (id, pack_id, text, intensity, partner_text, allowed_couple_genders, target_user_genders, required_props, deleted_at, inverse_of, created_at) VALUES (' ||
|
||||
quote_literal(id) || ', ' ||
|
||||
quote_literal(pack_id) || ', ' ||
|
||||
quote_literal(text) || ', ' ||
|
||||
COALESCE(intensity::text, 'NULL') || ', ' ||
|
||||
COALESCE(quote_literal(partner_text), 'NULL') || ', ' ||
|
||||
COALESCE(quote_literal(allowed_couple_genders::text), 'NULL') || ', ' ||
|
||||
COALESCE(quote_literal(target_user_genders::text), 'NULL') || ', ' ||
|
||||
COALESCE(quote_literal(required_props::text), 'NULL') || ', ' ||
|
||||
COALESCE(quote_literal(deleted_at::text), 'NULL') || ', ' ||
|
||||
'NULL, ' || -- inverse_of set to NULL initially
|
||||
quote_literal(created_at::text) ||
|
||||
') ON CONFLICT (id) DO UPDATE SET text = EXCLUDED.text, intensity = EXCLUDED.intensity, partner_text = EXCLUDED.partner_text, allowed_couple_genders = EXCLUDED.allowed_couple_genders, target_user_genders = EXCLUDED.target_user_genders, required_props = EXCLUDED.required_props;'
|
||||
FROM questions
|
||||
ORDER BY pack_id, intensity;
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "-- Update inverse_of references"
|
||||
|
||||
# Export inverse_of updates
|
||||
psql "$PROD_DB_URL" -t -A -c "
|
||||
SELECT 'UPDATE questions SET inverse_of = ' || quote_literal(inverse_of) || ' WHERE id = ' || quote_literal(id) || ';'
|
||||
FROM questions
|
||||
WHERE inverse_of IS NOT NULL;
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "-- Insert pack_topics"
|
||||
|
||||
# Export pack_topics
|
||||
psql "$PROD_DB_URL" -t -A -c "
|
||||
SELECT 'INSERT INTO pack_topics (pack_id, topic_id) VALUES (' ||
|
||||
quote_literal(pack_id) || ', ' ||
|
||||
quote_literal(topic_id) ||
|
||||
') ON CONFLICT DO NOTHING;'
|
||||
FROM pack_topics;
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "COMMIT;"
|
||||
echo ""
|
||||
echo "-- Done"
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Run content seed against non-production database using Node.js
|
||||
*
|
||||
* Usage:
|
||||
* export NONPROD_DATABASE_PASSWORD="your-database-password"
|
||||
* node scripts/run-seed-nonprod.mjs
|
||||
*
|
||||
* Get the password from: Supabase Dashboard > Settings > Database > Database password
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import pg from 'pg';
|
||||
|
||||
const { Client } = pg;
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Non-production database connection
|
||||
const NONPROD_PASSWORD = process.env.NONPROD_DATABASE_PASSWORD;
|
||||
|
||||
if (!NONPROD_PASSWORD) {
|
||||
console.error('Error: NONPROD_DATABASE_PASSWORD environment variable is not set');
|
||||
console.error('Get it from: Supabase Dashboard > Settings > Database > Database password');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const connectionString = `postgresql://postgres.itbzhrvlgvdmzbnhzhyx:${NONPROD_PASSWORD}@aws-0-eu-west-1.pooler.supabase.com:6543/postgres`;
|
||||
|
||||
async function main() {
|
||||
console.log('=== Running content seed against Sauci Non-Production ===\n');
|
||||
|
||||
// Read the seed file
|
||||
const seedFile = path.join(__dirname, '../apps/supabase/content-seed.sql');
|
||||
|
||||
if (!fs.existsSync(seedFile)) {
|
||||
console.error(`Error: Seed file not found at ${seedFile}`);
|
||||
console.error('Run the export from production first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sql = fs.readFileSync(seedFile, 'utf8');
|
||||
console.log(`Loaded seed file (${sql.split('\n').length} lines)`);
|
||||
|
||||
const client = new Client({ connectionString });
|
||||
|
||||
try {
|
||||
console.log('Connecting to non-production database...');
|
||||
await client.connect();
|
||||
console.log('Connected!\n');
|
||||
|
||||
console.log('Executing seed SQL...');
|
||||
await client.query(sql);
|
||||
|
||||
console.log('\n=== Content sync complete! ===');
|
||||
|
||||
// Verify the data
|
||||
console.log('\nVerifying data:');
|
||||
const tables = ['categories', 'topics', 'question_packs', 'dare_packs', 'questions', 'dares', 'pack_topics'];
|
||||
for (const table of tables) {
|
||||
const result = await client.query(`SELECT COUNT(*) as count FROM ${table}`);
|
||||
console.log(` - ${table}: ${result.rows[0].count} rows`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('\nError:', error.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Script to sync content from production to non-production Supabase
|
||||
*
|
||||
* Usage: npx ts-node scripts/sync-content-to-nonprod.ts
|
||||
*
|
||||
* Make sure you have the following environment variables set:
|
||||
* - PROD_SUPABASE_URL (or uses default)
|
||||
* - PROD_SUPABASE_SERVICE_KEY
|
||||
* - NONPROD_SUPABASE_URL (or uses default)
|
||||
* - NONPROD_SUPABASE_SERVICE_KEY
|
||||
*/
|
||||
|
||||
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
// Configuration
|
||||
const PROD_URL = process.env.PROD_SUPABASE_URL || 'https://ckjcrkjpmhqhiucifukx.supabase.co';
|
||||
const PROD_KEY = process.env.PROD_SUPABASE_SERVICE_KEY;
|
||||
|
||||
const NONPROD_URL = process.env.NONPROD_SUPABASE_URL || 'https://itbzhrvlgvdmzbnhzhyx.supabase.co';
|
||||
const NONPROD_KEY = process.env.NONPROD_SUPABASE_SERVICE_KEY;
|
||||
|
||||
if (!PROD_KEY || !NONPROD_KEY) {
|
||||
console.error('Missing required environment variables:');
|
||||
if (!PROD_KEY) console.error(' - PROD_SUPABASE_SERVICE_KEY');
|
||||
if (!NONPROD_KEY) console.error(' - NONPROD_SUPABASE_SERVICE_KEY');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const prodClient = createClient(PROD_URL, PROD_KEY);
|
||||
const nonprodClient = createClient(NONPROD_URL, NONPROD_KEY);
|
||||
|
||||
async function fetchAll<T>(client: SupabaseClient, table: string, orderBy?: string): Promise<T[]> {
|
||||
let allData: T[] = [];
|
||||
let offset = 0;
|
||||
const limit = 1000;
|
||||
|
||||
while (true) {
|
||||
let query = client.from(table).select('*').range(offset, offset + limit - 1);
|
||||
if (orderBy) {
|
||||
query = query.order(orderBy);
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Error fetching ${table}: ${error.message}`);
|
||||
}
|
||||
|
||||
if (!data || data.length === 0) break;
|
||||
|
||||
allData = allData.concat(data as T[]);
|
||||
offset += limit;
|
||||
|
||||
if (data.length < limit) break;
|
||||
}
|
||||
|
||||
return allData;
|
||||
}
|
||||
|
||||
async function clearTable(client: SupabaseClient, table: string) {
|
||||
// Use RPC to bypass RLS for deletion
|
||||
const { error } = await client.rpc('truncate_table', { table_name: table });
|
||||
if (error) {
|
||||
// If RPC doesn't exist, try direct delete
|
||||
const { error: deleteError } = await client.from(table).delete().gte('id', '00000000-0000-0000-0000-000000000000');
|
||||
if (deleteError) {
|
||||
console.warn(`Warning: Could not clear ${table}: ${deleteError.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function insertBatch<T extends Record<string, any>>(
|
||||
client: SupabaseClient,
|
||||
table: string,
|
||||
data: T[],
|
||||
batchSize = 100
|
||||
): Promise<void> {
|
||||
for (let i = 0; i < data.length; i += batchSize) {
|
||||
const batch = data.slice(i, i + batchSize);
|
||||
const { error } = await client.from(table).upsert(batch, { onConflict: 'id' });
|
||||
|
||||
if (error) {
|
||||
console.error(`Error inserting into ${table} at batch ${i / batchSize}:`, error.message);
|
||||
// Try inserting one by one to find the problematic row
|
||||
for (const row of batch) {
|
||||
const { error: singleError } = await client.from(table).upsert(row, { onConflict: 'id' });
|
||||
if (singleError) {
|
||||
console.error(` Failed row:`, row.id, singleError.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write(`\r Inserted ${Math.min(i + batchSize, data.length)}/${data.length}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('=== Content Sync: Production -> Non-Production ===\n');
|
||||
|
||||
try {
|
||||
// 1. Fetch all content from production
|
||||
console.log('Fetching content from production...');
|
||||
|
||||
console.log(' - Categories...');
|
||||
const categories = await fetchAll(prodClient, 'categories', 'sort_order');
|
||||
console.log(` Found ${categories.length} categories`);
|
||||
|
||||
console.log(' - Topics...');
|
||||
const topics = await fetchAll(prodClient, 'topics', 'sort_order');
|
||||
console.log(` Found ${topics.length} topics`);
|
||||
|
||||
console.log(' - Question Packs...');
|
||||
const questionPacks = await fetchAll(prodClient, 'question_packs', 'sort_order');
|
||||
console.log(` Found ${questionPacks.length} question packs`);
|
||||
|
||||
console.log(' - Dare Packs...');
|
||||
const darePacks = await fetchAll(prodClient, 'dare_packs', 'sort_order');
|
||||
console.log(` Found ${darePacks.length} dare packs`);
|
||||
|
||||
console.log(' - Questions...');
|
||||
const questions = await fetchAll(prodClient, 'questions', 'pack_id');
|
||||
console.log(` Found ${questions.length} questions`);
|
||||
|
||||
console.log(' - Dares...');
|
||||
const dares = await fetchAll(prodClient, 'dares', 'pack_id');
|
||||
console.log(` Found ${dares.length} dares`);
|
||||
|
||||
console.log(' - Pack Topics...');
|
||||
const packTopics = await fetchAll(prodClient, 'pack_topics');
|
||||
console.log(` Found ${packTopics.length} pack-topic associations`);
|
||||
|
||||
// 2. Clear existing content in non-prod (in reverse dependency order)
|
||||
console.log('\nClearing existing content in non-production...');
|
||||
console.log(' - pack_topics...');
|
||||
await clearTable(nonprodClient, 'pack_topics');
|
||||
console.log(' - questions...');
|
||||
await clearTable(nonprodClient, 'questions');
|
||||
console.log(' - dares...');
|
||||
await clearTable(nonprodClient, 'dares');
|
||||
console.log(' - question_packs...');
|
||||
await clearTable(nonprodClient, 'question_packs');
|
||||
console.log(' - dare_packs...');
|
||||
await clearTable(nonprodClient, 'dare_packs');
|
||||
console.log(' - topics...');
|
||||
await clearTable(nonprodClient, 'topics');
|
||||
console.log(' - categories...');
|
||||
await clearTable(nonprodClient, 'categories');
|
||||
|
||||
// 3. Insert content into non-prod (in dependency order)
|
||||
console.log('\nInserting content into non-production...');
|
||||
|
||||
console.log(' - Categories...');
|
||||
await insertBatch(nonprodClient, 'categories', categories);
|
||||
|
||||
console.log(' - Topics...');
|
||||
await insertBatch(nonprodClient, 'topics', topics);
|
||||
|
||||
console.log(' - Question Packs...');
|
||||
await insertBatch(nonprodClient, 'question_packs', questionPacks);
|
||||
|
||||
console.log(' - Dare Packs...');
|
||||
await insertBatch(nonprodClient, 'dare_packs', darePacks);
|
||||
|
||||
// Handle questions with inverse_of self-reference
|
||||
// First insert all questions with inverse_of set to NULL
|
||||
console.log(' - Questions (pass 1: without inverse_of)...');
|
||||
const questionsWithoutInverse = questions.map((q: any) => ({ ...q, inverse_of: null }));
|
||||
await insertBatch(nonprodClient, 'questions', questionsWithoutInverse);
|
||||
|
||||
// Then update the inverse_of references
|
||||
const questionsWithInverse = (questions as any[]).filter(q => q.inverse_of !== null);
|
||||
if (questionsWithInverse.length > 0) {
|
||||
console.log(` - Questions (pass 2: updating ${questionsWithInverse.length} inverse_of references)...`);
|
||||
for (const q of questionsWithInverse) {
|
||||
const { error } = await nonprodClient
|
||||
.from('questions')
|
||||
.update({ inverse_of: q.inverse_of })
|
||||
.eq('id', q.id);
|
||||
if (error) {
|
||||
console.error(` Failed to update inverse_of for ${q.id}:`, error.message);
|
||||
}
|
||||
}
|
||||
console.log(` Updated ${questionsWithInverse.length} references`);
|
||||
}
|
||||
|
||||
console.log(' - Dares...');
|
||||
await insertBatch(nonprodClient, 'dares', dares);
|
||||
|
||||
console.log(' - Pack Topics...');
|
||||
// pack_topics has composite primary key, need different upsert strategy
|
||||
for (const pt of packTopics as any[]) {
|
||||
const { error } = await nonprodClient
|
||||
.from('pack_topics')
|
||||
.upsert(pt, { onConflict: 'pack_id,topic_id' });
|
||||
if (error) {
|
||||
console.error(` Failed pack_topic:`, pt, error.message);
|
||||
}
|
||||
}
|
||||
console.log(` Inserted ${(packTopics as any[]).length} pack-topic associations`);
|
||||
|
||||
console.log('\n=== Sync Complete! ===');
|
||||
console.log(`
|
||||
Summary:
|
||||
- ${categories.length} categories
|
||||
- ${topics.length} topics
|
||||
- ${questionPacks.length} question packs
|
||||
- ${darePacks.length} dare packs
|
||||
- ${questions.length} questions
|
||||
- ${dares.length} dares
|
||||
- ${(packTopics as any[]).length} pack-topic associations
|
||||
`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Sync failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Sync content from production to non-production using direct PostgreSQL connection
|
||||
*
|
||||
* Usage:
|
||||
* export PROD_DATABASE_URL="postgresql://postgres.xxx:password@aws-0-eu-west-2.pooler.supabase.com:6543/postgres"
|
||||
* export NONPROD_DATABASE_URL="postgresql://postgres.xxx:password@aws-0-eu-west-1.pooler.supabase.com:6543/postgres"
|
||||
* node scripts/sync-content.mjs
|
||||
*
|
||||
* Get connection strings from: Supabase Dashboard > Settings > Database > Connection string (Transaction pooler)
|
||||
*/
|
||||
|
||||
import pg from 'pg';
|
||||
const { Client } = pg;
|
||||
|
||||
const PROD_URL = process.env.PROD_DATABASE_URL;
|
||||
const NONPROD_URL = process.env.NONPROD_DATABASE_URL;
|
||||
|
||||
if (!PROD_URL || !NONPROD_URL) {
|
||||
console.error('Missing required environment variables:');
|
||||
if (!PROD_URL) console.error(' - PROD_DATABASE_URL');
|
||||
if (!NONPROD_URL) console.error(' - NONPROD_DATABASE_URL');
|
||||
console.error('\nGet connection strings from: Supabase Dashboard > Settings > Database > Connection string (Transaction pooler)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('=== Content Sync: Production -> Non-Production ===\n');
|
||||
|
||||
const prodClient = new Client({ connectionString: PROD_URL });
|
||||
const nonprodClient = new Client({ connectionString: NONPROD_URL });
|
||||
|
||||
try {
|
||||
console.log('Connecting to databases...');
|
||||
await prodClient.connect();
|
||||
console.log(' - Connected to production');
|
||||
await nonprodClient.connect();
|
||||
console.log(' - Connected to non-production');
|
||||
|
||||
// Fetch all content from production
|
||||
console.log('\nFetching content from production...');
|
||||
|
||||
const categories = (await prodClient.query('SELECT * FROM categories ORDER BY sort_order')).rows;
|
||||
console.log(` - ${categories.length} categories`);
|
||||
|
||||
const topics = (await prodClient.query('SELECT * FROM topics ORDER BY sort_order')).rows;
|
||||
console.log(` - ${topics.length} topics`);
|
||||
|
||||
const questionPacks = (await prodClient.query('SELECT * FROM question_packs ORDER BY sort_order')).rows;
|
||||
console.log(` - ${questionPacks.length} question packs`);
|
||||
|
||||
const darePacks = (await prodClient.query('SELECT * FROM dare_packs ORDER BY sort_order')).rows;
|
||||
console.log(` - ${darePacks.length} dare packs`);
|
||||
|
||||
const questions = (await prodClient.query('SELECT * FROM questions ORDER BY pack_id')).rows;
|
||||
console.log(` - ${questions.length} questions`);
|
||||
|
||||
const dares = (await prodClient.query('SELECT * FROM dares ORDER BY pack_id')).rows;
|
||||
console.log(` - ${dares.length} dares`);
|
||||
|
||||
const packTopics = (await prodClient.query('SELECT * FROM pack_topics')).rows;
|
||||
console.log(` - ${packTopics.length} pack-topic associations`);
|
||||
|
||||
// Clear and insert in non-prod
|
||||
console.log('\nClearing existing content in non-production...');
|
||||
await nonprodClient.query('BEGIN');
|
||||
|
||||
await nonprodClient.query('DELETE FROM pack_topics');
|
||||
await nonprodClient.query('DELETE FROM questions');
|
||||
await nonprodClient.query('DELETE FROM dares');
|
||||
await nonprodClient.query('DELETE FROM question_packs');
|
||||
await nonprodClient.query('DELETE FROM dare_packs');
|
||||
await nonprodClient.query('DELETE FROM topics');
|
||||
await nonprodClient.query('DELETE FROM categories');
|
||||
console.log(' - Cleared all content tables');
|
||||
|
||||
// Insert categories
|
||||
console.log('\nInserting content into non-production...');
|
||||
for (const cat of categories) {
|
||||
await nonprodClient.query(
|
||||
`INSERT INTO categories (id, name, description, icon, sort_order, created_at, is_public)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
||||
[cat.id, cat.name, cat.description, cat.icon, cat.sort_order, cat.created_at, cat.is_public]
|
||||
);
|
||||
}
|
||||
console.log(` - Inserted ${categories.length} categories`);
|
||||
|
||||
// Insert topics
|
||||
for (const topic of topics) {
|
||||
await nonprodClient.query(
|
||||
`INSERT INTO topics (id, name, description, icon, sort_order, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[topic.id, topic.name, topic.description, topic.icon, topic.sort_order, topic.created_at]
|
||||
);
|
||||
}
|
||||
console.log(` - Inserted ${topics.length} topics`);
|
||||
|
||||
// Insert question_packs
|
||||
for (const pack of questionPacks) {
|
||||
await nonprodClient.query(
|
||||
`INSERT INTO question_packs (id, name, description, icon, is_premium, is_public, sort_order, created_at, category_id, is_explicit, min_intensity, max_intensity, avg_intensity, scheduled_release_at, release_notified)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)`,
|
||||
[pack.id, pack.name, pack.description, pack.icon, pack.is_premium, pack.is_public, pack.sort_order, pack.created_at, pack.category_id, pack.is_explicit, pack.min_intensity, pack.max_intensity, pack.avg_intensity, pack.scheduled_release_at, pack.release_notified]
|
||||
);
|
||||
}
|
||||
console.log(` - Inserted ${questionPacks.length} question packs`);
|
||||
|
||||
// Insert dare_packs
|
||||
for (const pack of darePacks) {
|
||||
await nonprodClient.query(
|
||||
`INSERT INTO dare_packs (id, name, description, icon, is_premium, is_public, is_explicit, sort_order, category_id, min_intensity, max_intensity, avg_intensity, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
||||
[pack.id, pack.name, pack.description, pack.icon, pack.is_premium, pack.is_public, pack.is_explicit, pack.sort_order, pack.category_id, pack.min_intensity, pack.max_intensity, pack.avg_intensity, pack.created_at]
|
||||
);
|
||||
}
|
||||
console.log(` - Inserted ${darePacks.length} dare packs`);
|
||||
|
||||
// Insert questions (first pass without inverse_of)
|
||||
let questionCount = 0;
|
||||
for (const q of questions) {
|
||||
await nonprodClient.query(
|
||||
`INSERT INTO questions (id, pack_id, text, intensity, partner_text, allowed_couple_genders, target_user_genders, required_props, deleted_at, inverse_of, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NULL, $10)`,
|
||||
[q.id, q.pack_id, q.text, q.intensity, q.partner_text, q.allowed_couple_genders, q.target_user_genders, q.required_props, q.deleted_at, q.created_at]
|
||||
);
|
||||
questionCount++;
|
||||
if (questionCount % 100 === 0) {
|
||||
process.stdout.write(`\r - Inserting questions: ${questionCount}/${questions.length}`);
|
||||
}
|
||||
}
|
||||
console.log(`\r - Inserted ${questions.length} questions`);
|
||||
|
||||
// Update inverse_of references
|
||||
const questionsWithInverse = questions.filter(q => q.inverse_of !== null);
|
||||
if (questionsWithInverse.length > 0) {
|
||||
for (const q of questionsWithInverse) {
|
||||
await nonprodClient.query(
|
||||
'UPDATE questions SET inverse_of = $1 WHERE id = $2',
|
||||
[q.inverse_of, q.id]
|
||||
);
|
||||
}
|
||||
console.log(` - Updated ${questionsWithInverse.length} inverse_of references`);
|
||||
}
|
||||
|
||||
// Insert dares
|
||||
for (const dare of dares) {
|
||||
await nonprodClient.query(
|
||||
`INSERT INTO dares (id, pack_id, text, intensity, suggested_duration_hours, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[dare.id, dare.pack_id, dare.text, dare.intensity, dare.suggested_duration_hours, dare.created_at]
|
||||
);
|
||||
}
|
||||
console.log(` - Inserted ${dares.length} dares`);
|
||||
|
||||
// Insert pack_topics
|
||||
for (const pt of packTopics) {
|
||||
await nonprodClient.query(
|
||||
`INSERT INTO pack_topics (pack_id, topic_id) VALUES ($1, $2)`,
|
||||
[pt.pack_id, pt.topic_id]
|
||||
);
|
||||
}
|
||||
console.log(` - Inserted ${packTopics.length} pack-topic associations`);
|
||||
|
||||
await nonprodClient.query('COMMIT');
|
||||
|
||||
console.log('\n=== Sync Complete! ===');
|
||||
console.log(`
|
||||
Summary:
|
||||
- ${categories.length} categories
|
||||
- ${topics.length} topics
|
||||
- ${questionPacks.length} question packs
|
||||
- ${darePacks.length} dare packs
|
||||
- ${questions.length} questions
|
||||
- ${dares.length} dares
|
||||
- ${packTopics.length} pack-topic associations
|
||||
`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('\nSync failed:', error.message);
|
||||
try {
|
||||
await nonprodClient.query('ROLLBACK');
|
||||
} catch (e) {}
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await prodClient.end();
|
||||
await nonprodClient.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user