2026-03-08 22:43:51 -05:00
|
|
|
const Database = require('better-sqlite3');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
|
|
|
function initDatabase(dbPath) {
|
|
|
|
|
// Ensure data directory exists
|
|
|
|
|
const dir = path.dirname(dbPath);
|
|
|
|
|
if (!fs.existsSync(dir)) {
|
|
|
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const db = new Database(dbPath);
|
|
|
|
|
|
|
|
|
|
// Enable foreign keys
|
|
|
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
|
|
|
|
|
|
console.log('Initializing database schema...');
|
|
|
|
|
|
2026-03-09 01:59:52 -05:00
|
|
|
// Dogs table - NO sire/dam columns, only litter_id
|
2026-03-08 22:43:51 -05:00
|
|
|
db.exec(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS dogs (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
registration_number TEXT UNIQUE,
|
|
|
|
|
breed TEXT NOT NULL,
|
|
|
|
|
sex TEXT NOT NULL CHECK(sex IN ('male', 'female')),
|
|
|
|
|
birth_date DATE,
|
|
|
|
|
color TEXT,
|
2026-03-08 23:39:22 -05:00
|
|
|
microchip TEXT,
|
2026-03-08 22:43:51 -05:00
|
|
|
photo_urls TEXT, -- JSON array of photo URLs
|
|
|
|
|
notes TEXT,
|
2026-03-09 01:59:52 -05:00
|
|
|
litter_id INTEGER,
|
2026-03-08 22:43:51 -05:00
|
|
|
is_active INTEGER DEFAULT 1,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
2026-03-09 01:59:52 -05:00
|
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
FOREIGN KEY (litter_id) REFERENCES litters(id) ON DELETE SET NULL
|
2026-03-08 22:43:51 -05:00
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
2026-03-08 23:39:22 -05:00
|
|
|
// Create unique index for microchip that allows NULL values
|
|
|
|
|
db.exec(`
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_dogs_microchip
|
|
|
|
|
ON dogs(microchip)
|
|
|
|
|
WHERE microchip IS NOT NULL
|
|
|
|
|
`);
|
|
|
|
|
|
2026-03-09 01:59:52 -05:00
|
|
|
// Parents table - Stores sire/dam relationships
|
2026-03-08 22:43:51 -05:00
|
|
|
db.exec(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS parents (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
dog_id INTEGER NOT NULL,
|
|
|
|
|
parent_id INTEGER NOT NULL,
|
|
|
|
|
parent_type TEXT NOT NULL CHECK(parent_type IN ('sire', 'dam')),
|
|
|
|
|
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
|
|
|
|
FOREIGN KEY (parent_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
2026-03-09 01:59:52 -05:00
|
|
|
UNIQUE(dog_id, parent_type)
|
2026-03-08 22:43:51 -05:00
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Litters table - Breeding records
|
|
|
|
|
db.exec(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS litters (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
sire_id INTEGER NOT NULL,
|
|
|
|
|
dam_id INTEGER NOT NULL,
|
|
|
|
|
breeding_date DATE NOT NULL,
|
|
|
|
|
whelping_date DATE,
|
|
|
|
|
puppy_count INTEGER DEFAULT 0,
|
|
|
|
|
notes TEXT,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
FOREIGN KEY (sire_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
|
|
|
|
FOREIGN KEY (dam_id) REFERENCES dogs(id) ON DELETE CASCADE
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Health records table
|
|
|
|
|
db.exec(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS health_records (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
dog_id INTEGER NOT NULL,
|
|
|
|
|
record_type TEXT NOT NULL CHECK(record_type IN ('test', 'vaccination', 'exam', 'treatment', 'certification')),
|
|
|
|
|
test_name TEXT,
|
|
|
|
|
test_date DATE NOT NULL,
|
|
|
|
|
result TEXT,
|
|
|
|
|
document_url TEXT,
|
|
|
|
|
notes TEXT,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Heat cycles table
|
|
|
|
|
db.exec(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS heat_cycles (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
dog_id INTEGER NOT NULL,
|
|
|
|
|
start_date DATE NOT NULL,
|
|
|
|
|
end_date DATE,
|
|
|
|
|
progesterone_peak_date DATE,
|
|
|
|
|
breeding_date DATE,
|
|
|
|
|
breeding_successful INTEGER DEFAULT 0,
|
|
|
|
|
notes TEXT,
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Traits table - Genetic trait tracking
|
|
|
|
|
db.exec(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS traits (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
dog_id INTEGER NOT NULL,
|
|
|
|
|
trait_category TEXT NOT NULL,
|
|
|
|
|
trait_name TEXT NOT NULL,
|
|
|
|
|
trait_value TEXT NOT NULL,
|
|
|
|
|
inherited_from INTEGER,
|
|
|
|
|
notes TEXT,
|
|
|
|
|
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE,
|
|
|
|
|
FOREIGN KEY (inherited_from) REFERENCES dogs(id) ON DELETE SET NULL
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Create indexes for performance
|
|
|
|
|
db.exec(`
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_dogs_name ON dogs(name);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_dogs_registration ON dogs(registration_number);
|
2026-03-09 01:59:52 -05:00
|
|
|
CREATE INDEX IF NOT EXISTS idx_dogs_litter ON dogs(litter_id);
|
2026-03-08 22:43:51 -05:00
|
|
|
CREATE INDEX IF NOT EXISTS idx_parents_dog ON parents(dog_id);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_parents_parent ON parents(parent_id);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_litters_sire ON litters(sire_id);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_litters_dam ON litters(dam_id);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_health_dog ON health_records(dog_id);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_heat_dog ON heat_cycles(dog_id);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_traits_dog ON traits(dog_id);
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Create trigger for updated_at
|
|
|
|
|
db.exec(`
|
|
|
|
|
CREATE TRIGGER IF NOT EXISTS update_dogs_timestamp
|
|
|
|
|
AFTER UPDATE ON dogs
|
|
|
|
|
FOR EACH ROW
|
|
|
|
|
BEGIN
|
|
|
|
|
UPDATE dogs SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
|
|
|
END;
|
|
|
|
|
`);
|
|
|
|
|
|
2026-03-09 01:59:52 -05:00
|
|
|
console.log('✓ Database schema initialized successfully!');
|
|
|
|
|
console.log('✓ Dogs table: NO sire/dam columns, uses parents table');
|
|
|
|
|
console.log('✓ Parents table: Stores sire/dam relationships');
|
|
|
|
|
console.log('✓ Litters table: Links puppies via litter_id');
|
2026-03-08 22:43:51 -05:00
|
|
|
|
|
|
|
|
db.close();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getDatabase() {
|
|
|
|
|
const dbPath = process.env.DB_PATH || path.join(__dirname, '../../data/breedr.db');
|
|
|
|
|
const db = new Database(dbPath);
|
|
|
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
|
return db;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { initDatabase, getDatabase };
|
|
|
|
|
|
|
|
|
|
// Run initialization if called directly
|
|
|
|
|
if (require.main === module) {
|
|
|
|
|
const dbPath = process.env.DB_PATH || path.join(__dirname, '../../data/breedr.db');
|
2026-03-09 01:59:52 -05:00
|
|
|
console.log('\n==========================================');
|
|
|
|
|
console.log('BREEDR Database Initialization');
|
|
|
|
|
console.log('==========================================');
|
|
|
|
|
console.log(`Database: ${dbPath}`);
|
|
|
|
|
console.log('==========================================\n');
|
2026-03-08 22:43:51 -05:00
|
|
|
initDatabase(dbPath);
|
2026-03-09 01:59:52 -05:00
|
|
|
console.log('\n✓ Database ready!\n');
|
|
|
|
|
}
|