2026-05-09 13:03:36 -05:00
#!/usr/bin/env node
/ * *
* Seed Demo Data Script
2026-05-16 11:42:32 -05:00
* Creates realistic bills across common categories for demo purposes .
2026-05-09 13:03:36 -05:00
* 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' ,
2026-05-14 02:11:54 -05:00
'Credit Cards' ,
'Loans' ,
2026-05-09 13:03:36 -05:00
'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 } ,
2026-05-14 02:11:54 -05:00
{ name : 'Mortgage' , category : 'Housing' , amount : 1200 , dueDay : 1 , cycle : 'monthly' , autopay : true , interestRate : 3.25 , currentBalance : 185000 , minPayment : 1200 , snowballOrder : 3 , snowballInclude : 0 } ,
2026-05-09 13:03:36 -05:00
{ 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 } ,
2026-05-16 11:42:32 -05:00
{ name : 'Discover It Card' , category : 'Credit Cards' , amount : 65 , dueDay : 26 , cycle : 'monthly' , autopay : true , interestRate : 22.99 , currentBalance : 920 , minPayment : 35 , snowballOrder : 0 , snowballInclude : 1 } ,
{ name : 'Capital One Quicksilver' , category : 'Credit Cards' , amount : 95 , dueDay : 28 , cycle : 'monthly' , autopay : true , interestRate : 24.49 , currentBalance : 1850 , minPayment : 55 , snowballOrder : 1 , snowballInclude : 1 } ,
{ name : 'Chase Freedom' , category : 'Credit Cards' , amount : 140 , dueDay : 12 , cycle : 'monthly' , autopay : true , interestRate : 21.49 , currentBalance : 3200 , minPayment : 90 , snowballOrder : 2 , snowballInclude : 1 } ,
{ name : 'Student Loan' , category : 'Loans' , amount : 250 , dueDay : 15 , cycle : 'monthly' , autopay : true , interestRate : 5.5 , currentBalance : 12500 , minPayment : 150 , snowballOrder : 4 , snowballInclude : 1 } ,
2026-05-09 13:03:36 -05:00
{ 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 } ,
2026-05-16 11:42:32 -05:00
{ name : 'Car Payment' , category : 'Loans' , amount : 350 , dueDay : 22 , cycle : 'monthly' , autopay : true , interestRate : 4.5 , currentBalance : 8400 , minPayment : 350 , snowballOrder : 3 , snowballInclude : 1 } ,
2026-05-09 13:03:36 -05:00
{ 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 ,
2026-05-14 02:11:54 -05:00
expected _amount , autopay _enabled , interest _rate ,
2026-05-14 03:00:01 -05:00
current _balance , minimum _payment , snowball _order , snowball _include , snowball _exempt ,
2026-05-14 02:11:54 -05:00
active , is _seeded )
2026-05-14 03:00:01 -05:00
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , 1 , 1 )
2026-05-09 13:03:36 -05:00
` );
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 (
2026-05-10 15:11:02 -05:00
targetUserId ,
2026-05-09 13:03:36 -05:00
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 ,
2026-05-16 11:42:32 -05:00
billData . interestRate ? ? ( Math . random ( ) > 0.7 ? Math . round ( Math . random ( ) * 15 * 100 ) / 100 : 0 ) ,
2026-05-14 02:11:54 -05:00
billData . currentBalance ? ? null ,
billData . minPayment ? ? null ,
billData . snowballOrder ? ? null ,
2026-05-14 03:00:01 -05:00
billData . snowballInclude ? ? 0 ,
billData . snowballExempt ? ? 0
2026-05-09 13:03:36 -05:00
) ;
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 } ;