Fix schema drift: fresh installs were broken
- Create heat_cycles table in init.js (breeding.js used it but nothing created it) - Add litters columns the routes actually use (breeding_date, whelping_date, puppy_count) - Add health_records columns used by puppy logs (record_date, description) - Puppy log insert now also sets NOT NULL test_date - Migration 002 no-ops on a fresh DB instead of crashing on missing dogs table - init.js honors DB_PATH and creates the data dir, so migrations and the app always target the same file Verified: full heat-cycle/litter/puppy-log CRUD passes against a virgin database. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+37
-2
@@ -2,8 +2,11 @@ const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const dbPath = path.join(__dirname, '../../data');
|
||||
const db = new Database(path.join(dbPath, 'breedr.db'));
|
||||
// Honor DB_PATH (used by Docker and the migration runner in index.js) so
|
||||
// migrations and the app always operate on the same file.
|
||||
const dbFile = process.env.DB_PATH || path.join(__dirname, '../../data/breedr.db');
|
||||
fs.mkdirSync(path.dirname(dbFile), { recursive: true });
|
||||
const db = new Database(dbFile);
|
||||
|
||||
function getDatabase() {
|
||||
return db;
|
||||
@@ -91,6 +94,9 @@ function initDatabase() {
|
||||
male_count INTEGER DEFAULT 0,
|
||||
female_count INTEGER DEFAULT 0,
|
||||
stillborn_count INTEGER DEFAULT 0,
|
||||
breeding_date TEXT,
|
||||
whelping_date TEXT,
|
||||
puppy_count INTEGER DEFAULT 0,
|
||||
notes TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
@@ -100,6 +106,33 @@ function initDatabase() {
|
||||
)
|
||||
`);
|
||||
|
||||
// migrate: columns the litters routes use (routes/litters.js) that older
|
||||
// schemas lack
|
||||
const litterMigrations = [
|
||||
['breeding_date', 'TEXT'],
|
||||
['whelping_date', 'TEXT'],
|
||||
['puppy_count', 'INTEGER DEFAULT 0'],
|
||||
];
|
||||
for (const [col, def] of litterMigrations) {
|
||||
try { db.exec(`ALTER TABLE litters ADD COLUMN ${col} ${def}`); } catch (_) { /* already exists */ }
|
||||
}
|
||||
|
||||
// ── Heat Cycles ───────────────────────────────────────────────────────────
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS heat_cycles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
dog_id INTEGER NOT NULL,
|
||||
start_date TEXT NOT NULL,
|
||||
end_date TEXT,
|
||||
breeding_date TEXT,
|
||||
breeding_successful INTEGER DEFAULT 0,
|
||||
notes TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (dog_id) REFERENCES dogs(id)
|
||||
)
|
||||
`);
|
||||
|
||||
// ── Health Records (OFA-extended) ─────────────────────────────────────────
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS health_records (
|
||||
@@ -135,6 +168,8 @@ function initDatabase() {
|
||||
['result', 'TEXT'],
|
||||
['vet_name', 'TEXT'],
|
||||
['next_due', 'TEXT'],
|
||||
['record_date', 'TEXT'],
|
||||
['description', 'TEXT'],
|
||||
];
|
||||
for (const [col, def] of healthMigrations) {
|
||||
try { db.exec(`ALTER TABLE health_records ADD COLUMN ${col} ${def}`); } catch (_) { /* already exists */ }
|
||||
|
||||
+13
-1
@@ -52,6 +52,13 @@ class MigrationRunner {
|
||||
return columns.some(col => col.name === 'sire' || col.name === 'dam');
|
||||
}
|
||||
|
||||
// Check if a table exists (fresh DBs have none — migrations must no-op)
|
||||
tableExists(name) {
|
||||
return !!this.db.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?"
|
||||
).get(name);
|
||||
}
|
||||
|
||||
// Check if litter_id column exists
|
||||
hasLitterIdColumn() {
|
||||
const columns = this.db.prepare("PRAGMA table_info(dogs)").all();
|
||||
@@ -187,7 +194,12 @@ class MigrationRunner {
|
||||
// Migration 2: Add litter_id column if missing
|
||||
migration002_addLitterIdColumn() {
|
||||
console.log('[Migration 002] Checking for litter_id column...');
|
||||
|
||||
|
||||
if (!this.tableExists('dogs')) {
|
||||
console.log('[Migration 002] Fresh database (no dogs table yet), skipping — init will create it');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hasLitterIdColumn()) {
|
||||
console.log('[Migration 002] litter_id column already exists, skipping');
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user