diff --git a/DATABASE.md b/DATABASE.md new file mode 100644 index 0000000..673c2af --- /dev/null +++ b/DATABASE.md @@ -0,0 +1,222 @@ +# BREEDR Database Schema + +## Overview + +This document describes the clean database schema for BREEDR. **NO migrations** - fresh installs create the correct schema automatically. + +## Schema Design + +### Core Principle: Parents Table Approach + +The `dogs` table **does NOT have sire/dam columns**. Parent relationships are stored in the separate `parents` table. This design: +- Keeps the schema clean and normalized +- Allows flexible parent relationships +- Supports future extensions (multiple sires, surrogates, etc.) + +## Tables + +### dogs + +Core registry for all dogs. + +```sql +CREATE TABLE 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, + microchip TEXT, + photo_urls TEXT, -- JSON array + notes TEXT, + litter_id INTEGER, -- Links to litters table + is_active INTEGER DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (litter_id) REFERENCES litters(id) ON DELETE SET NULL +); +``` + +**Important:** NO `sire_id` or `dam_id` columns! + +### parents + +Stores sire/dam relationships. + +```sql +CREATE TABLE parents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + dog_id INTEGER NOT NULL, -- The puppy + parent_id INTEGER NOT NULL, -- The parent + 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, + UNIQUE(dog_id, parent_type) -- One sire, one dam per dog +); +``` + +### litters + +Breeding records and litter tracking. + +```sql +CREATE TABLE 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 + +Health tests, vaccinations, exams, treatments. + +```sql +CREATE TABLE 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 + +Female heat cycle tracking for breeding timing. + +```sql +CREATE TABLE 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 + +Genetic trait tracking and inheritance. + +```sql +CREATE TABLE 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, -- Parent dog ID + notes TEXT, + FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE, + FOREIGN KEY (inherited_from) REFERENCES dogs(id) ON DELETE SET NULL +); +``` + +## API Usage + +### Creating a Dog with Parents + +```javascript +POST /api/dogs +{ + "name": "Puppy Name", + "breed": "Breed Name", + "sex": "male", + "sire_id": 5, // Parent male dog ID + "dam_id": 8, // Parent female dog ID + "litter_id": 2 // Optional: link to litter +} +``` + +The API route automatically: +1. Inserts the dog into `dogs` table (without sire/dam columns) +2. Creates entries in `parents` table linking to sire and dam + +### Querying Parents + +```sql +-- Get a dog's parents +SELECT p.parent_type, d.* +FROM parents p +JOIN dogs d ON p.parent_id = d.id +WHERE p.dog_id = ?; + +-- Get a dog's offspring +SELECT d.* +FROM dogs d +JOIN parents p ON d.id = p.dog_id +WHERE p.parent_id = ?; +``` + +## Fresh Install + +For a fresh install: + +1. **Delete the old database** (if upgrading): + ```bash + rm data/breedr.db + ``` + +2. **Start the server** - it will create the correct schema automatically: + ```bash + npm run dev + ``` + +3. **Verify the schema**: + ```bash + sqlite3 data/breedr.db ".schema dogs" + ``` + + You should see `litter_id` but **NO** `sire_id` or `dam_id` columns. + +## Troubleshooting + +### "no such column: weight" or "no such column: sire_id" + +**Solution:** Your database has an old schema. Delete it and let the app recreate it: + +```bash +rm data/breedr.db +npm run dev +``` + +### Parent relationships not saving + +Check server logs. You should see: +``` +✓ Dog inserted with ID: 123 + Adding sire relationship: dog 123 -> sire 5 + ✓ Sire relationship added + Adding dam relationship: dog 123 -> dam 8 + ✓ Dam relationship added +``` + +If relationships aren't being created, check that `sire_id` and `dam_id` are being sent in the API request. + +## Database Files + +- `server/db/init.js` - Creates clean schema, no migrations +- `server/routes/dogs.js` - Handles parent relationships via `parents` table +- `server/index.js` - Initializes database on startup + +**NO MIGRATIONS!** The init file is the source of truth.