#!/usr/bin/env node /** * Seed Demo Data Script * Creates 20 realistic bills across 8 categories for demo purposes. * Idempotent: can be run multiple times safely. */ const path = require('path'); // Use DB_PATH from env or default to db/bills.db const DB_PATH = process.env.DB_PATH || path.join(__dirname, '..', 'db', 'bills.db'); // Import database helper const { getDb, ensureUserDefaultCategories } = require('../db/database'); const CATEGORIES = [ 'Utilities', 'Housing', 'Insurance', 'Subscriptions', 'Transportation', 'Healthcare', 'Finance', 'Entertainment', ]; // Real-world bill names with realistic data const BILLS = [ { name: 'Electric Company', category: 'Utilities', amount: 85, dueDay: 15, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'City Water Dept', category: 'Utilities', amount: 45, dueDay: 20, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Rent/Mortgage', category: 'Housing', amount: 1200, dueDay: 1, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Car Insurance', category: 'Insurance', amount: 120, dueDay: 5, cycle: 'quarterly', autopay: true, interestRate: 0 }, { name: 'Netflix', category: 'Subscriptions', amount: 15.99, dueDay: 22, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Gym Membership', category: 'Subscriptions', amount: 45, dueDay: 10, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Internet Provider', category: 'Utilities', amount: 70, dueDay: 18, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Cell Phone', category: 'Utilities', amount: 65, dueDay: 25, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Health Insurance', category: 'Healthcare', amount: 200, dueDay: 1, cycle: 'quarterly', autopay: true, interestRate: 0 }, { name: 'Credit Card', category: 'Finance', amount: 150, dueDay: 28, cycle: 'monthly', autopay: true, interestRate: 19.99 }, { name: 'Student Loan', category: 'Finance', amount: 250, dueDay: 15, cycle: 'monthly', autopay: true, interestRate: 5.5 }, { name: 'Gas Utility', category: 'Utilities', amount: 35, dueDay: 12, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Trash Service', category: 'Utilities', amount: 25, dueDay: 28, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Homeowners Insurance', category: 'Insurance', amount: 300, dueDay: 10, cycle: 'annually', autopay: false, interestRate: 0 }, { name: 'Car Payment', category: 'Finance', amount: 350, dueDay: 22, cycle: 'monthly', autopay: true, interestRate: 4.5 }, { name: 'Spotify', category: 'Entertainment', amount: 9.99, dueDay: 14, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Adobe Creative Cloud', category: 'Subscriptions', amount: 54.99, dueDay: 8, cycle: 'monthly', autopay: true, interestRate: 0 }, { name: 'Amazon Prime', category: 'Subscriptions', amount: 14.99, dueDay: 1, cycle: 'annually', autopay: true, interestRate: 0 }, { name: 'Grocery Delivery', category: 'Entertainment', amount: 30, dueDay: 3, cycle: 'irregular', autopay: false, interestRate: 0 }, { name: 'Dental Insurance', category: 'Healthcare', amount: 40, dueDay: 15, cycle: 'quarterly', autopay: true, interestRate: 0 }, ]; /** * Get or create a category by name for a user */ function getCategoryByName(db, userId, name) { let category = db.prepare('SELECT id FROM categories WHERE user_id = ? AND LOWER(name) = LOWER(?)').get(userId, name); if (!category) { const result = db.prepare('INSERT INTO categories (user_id, name) VALUES (?, ?)').run(userId, name); category = { id: result.lastInsertRowid }; } return category; } /** * Generate realistic random amounts based on type */ function getRandomAmount(min, max) { const range = max - min; const randomValue = Math.random() * range + min; return Math.round(randomValue * 100) / 100; } /** * Main seed function * @param {number} [userId] - User ID to seed data for. If not provided, uses the first admin user. */ function seedDemoData(userId = null) { const db = getDb(); // Check if data already exists for this user (if userId provided) or globally let existingCheck; if (userId !== null) { existingCheck = db.prepare('SELECT COUNT(*) AS count FROM bills WHERE user_id = ?').get(userId); } else { existingCheck = db.prepare('SELECT COUNT(*) AS count FROM bills').get(); } if (existingCheck.count > 0) { console.log(`⚠️ Found ${existingCheck.count} existing bills. Skipping seed to prevent duplicates.`); console.log(' Run again with --force to overwrite.'); return { billsCreated: 0, categoriesCreated: 0, message: 'Data already exists' }; } // Get user (or admin if userId not provided) let targetUser; if (userId !== null) { targetUser = db.prepare('SELECT id FROM users WHERE id = ?').get(userId); } else { targetUser = db.prepare('SELECT id FROM users WHERE role = ? ORDER BY id LIMIT 1', 'admin').get(); } if (!targetUser) { throw new Error('User not found. Please create a user first.'); } const targetUserId = targetUser.id; console.log(`📝 Seeding demo data for user: ${targetUserId}`); // Ensure default categories exist for this user ensureUserDefaultCategories(targetUserId); // Create our 8 demo categories if they don't exist const categoriesMap = {}; let categoriesCreated = 0; for (const categoryName of CATEGORIES) { const category = getCategoryByName(db, targetUserId, categoryName); categoriesMap[categoryName] = category.id; // Tag seeded categories db.prepare('UPDATE categories SET is_seeded = 1 WHERE id = ?').run(category.id); if (category.id > (db.prepare('SELECT id FROM categories WHERE user_id = ? AND name = ?').get(targetUserId, categoryName)?.id || 0)) { categoriesCreated++; } } // Create bills let billsCreated = 0; const insertBill = db.prepare(` INSERT INTO bills (user_id, name, category_id, due_day, billing_cycle, expected_amount, autopay_enabled, interest_rate, active, is_seeded) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, 1) `); for (const billData of BILLS) { const category = categoriesMap[billData.category]; // Use provided amount or generate random within range const amount = billData.amount || getRandomAmount(15, 2500); try { insertBill.run( userId, billData.name, category, billData.dueDay || Math.floor(Math.random() * 28) + 1, billData.cycle || 'monthly', amount, billData.autopay !== undefined ? (billData.autopay ? 1 : 0) : Math.random() > 0.5 ? 1 : 0, billData.interestRate || (Math.random() > 0.7 ? Math.round(Math.random() * 15 * 100) / 100 : 0) ); billsCreated++; } catch (err) { console.error(`Failed to create bill "${billData.name}":`, err.message); } } console.log(`✅ Created ${billsCreated} demo bills`); console.log(`✅ Created ${categoriesCreated} demo categories`); return { billsCreated, categoriesCreated }; } // Run seed if called directly if (require.main === module) { try { const result = seedDemoData(); console.log('\nSeed complete:', result); process.exit(0); } catch (err) { console.error('Seed failed:', err.message); process.exit(1); } } module.exports = { seedDemoData };