BillTracker/scripts/seedDemoData.js

175 lines
7.2 KiB
JavaScript

#!/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 };