Merge pull request 'feature/enhanced-litters-and-pedigree' (#15) from feature/enhanced-litters-and-pedigree into master
Reviewed-on: #15
This commit was merged in pull request #15.
This commit is contained in:
222
DATABASE.md
Normal file
222
DATABASE.md
Normal file
@@ -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.
|
||||
@@ -16,7 +16,7 @@ function initDatabase(dbPath) {
|
||||
|
||||
console.log('Initializing database schema...');
|
||||
|
||||
// Dogs table - Core registry
|
||||
// Dogs table - NO sire/dam columns, only litter_id
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS dogs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -29,9 +29,11 @@ function initDatabase(dbPath) {
|
||||
microchip TEXT,
|
||||
photo_urls TEXT, -- JSON array of photo URLs
|
||||
notes TEXT,
|
||||
litter_id INTEGER,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (litter_id) REFERENCES litters(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
|
||||
@@ -42,7 +44,7 @@ function initDatabase(dbPath) {
|
||||
WHERE microchip IS NOT NULL
|
||||
`);
|
||||
|
||||
// Parents table - Relationship mapping
|
||||
// Parents table - Stores sire/dam relationships
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS parents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -51,7 +53,7 @@ function initDatabase(dbPath) {
|
||||
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_id, parent_type)
|
||||
UNIQUE(dog_id, parent_type)
|
||||
)
|
||||
`);
|
||||
|
||||
@@ -122,6 +124,7 @@ function initDatabase(dbPath) {
|
||||
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);
|
||||
CREATE INDEX IF NOT EXISTS idx_dogs_litter ON dogs(litter_id);
|
||||
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);
|
||||
@@ -141,7 +144,10 @@ function initDatabase(dbPath) {
|
||||
END;
|
||||
`);
|
||||
|
||||
console.log('Database schema initialized successfully!');
|
||||
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');
|
||||
|
||||
db.close();
|
||||
return true;
|
||||
@@ -159,5 +165,11 @@ 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');
|
||||
console.log('\n==========================================');
|
||||
console.log('BREEDR Database Initialization');
|
||||
console.log('==========================================');
|
||||
console.log(`Database: ${dbPath}`);
|
||||
console.log('==========================================\n');
|
||||
initDatabase(dbPath);
|
||||
console.log('\n✓ Database ready!\n');
|
||||
}
|
||||
@@ -4,7 +4,6 @@ const helmet = require('helmet');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { initDatabase } = require('./db/init');
|
||||
const { runMigrations } = require('./db/migrations');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
@@ -21,20 +20,9 @@ if (!fs.existsSync(UPLOAD_PATH)) {
|
||||
}
|
||||
|
||||
// Initialize database schema (creates tables if they don't exist)
|
||||
console.log('Initializing database...');
|
||||
initDatabase(DB_PATH);
|
||||
|
||||
// Run migrations to ensure schema is up-to-date
|
||||
try {
|
||||
console.log('Running database migrations...');
|
||||
runMigrations(DB_PATH);
|
||||
console.log('Database migrations complete!\n');
|
||||
} catch (error) {
|
||||
console.error('\n⚠️ Database migration failed!');
|
||||
console.error('Error:', error.message);
|
||||
console.error('\nThe application may not function correctly.');
|
||||
console.error('Please check the database and try again.\n');
|
||||
// Don't exit - let the app try to start anyway
|
||||
}
|
||||
console.log('✓ Database ready!\n');
|
||||
|
||||
// Middleware
|
||||
app.use(helmet({
|
||||
@@ -81,13 +69,13 @@ app.use((err, req, res, next) => {
|
||||
// Start server
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`\n🐕 BREEDR Server Running`);
|
||||
console.log(`=============================`);
|
||||
console.log(`==============================`);
|
||||
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
||||
console.log(`Port: ${PORT}`);
|
||||
console.log(`Database: ${DB_PATH}`);
|
||||
console.log(`Uploads: ${UPLOAD_PATH}`);
|
||||
console.log(`Access: http://localhost:${PORT}`);
|
||||
console.log(`=============================\n`);
|
||||
console.log(`==============================\n`);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@@ -41,10 +41,9 @@ const emptyToNull = (value) => {
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
// Select only fields that exist in the schema (no weight/height)
|
||||
const dogs = db.prepare(`
|
||||
SELECT id, name, registration_number, breed, sex, birth_date,
|
||||
color, microchip, photo_urls, notes, is_active,
|
||||
color, microchip, photo_urls, notes, litter_id, is_active,
|
||||
created_at, updated_at
|
||||
FROM dogs
|
||||
WHERE is_active = 1
|
||||
@@ -58,18 +57,18 @@ router.get('/', (req, res) => {
|
||||
|
||||
res.json(dogs);
|
||||
} catch (error) {
|
||||
console.error('Error fetching dogs:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET single dog by ID
|
||||
// GET single dog by ID with parents and offspring
|
||||
router.get('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
// Select only fields that exist in the schema (no weight/height)
|
||||
const dog = db.prepare(`
|
||||
SELECT id, name, registration_number, breed, sex, birth_date,
|
||||
color, microchip, photo_urls, notes, is_active,
|
||||
color, microchip, photo_urls, notes, litter_id, is_active,
|
||||
created_at, updated_at
|
||||
FROM dogs
|
||||
WHERE id = ?
|
||||
@@ -101,6 +100,7 @@ router.get('/:id', (req, res) => {
|
||||
|
||||
res.json(dog);
|
||||
} catch (error) {
|
||||
console.error('Error fetching dog:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -110,77 +110,64 @@ router.post('/', (req, res) => {
|
||||
try {
|
||||
const { name, registration_number, breed, sex, birth_date, color, microchip, notes, sire_id, dam_id, litter_id } = req.body;
|
||||
|
||||
console.log('Creating dog with data:', { name, breed, sex, sire_id, dam_id, litter_id });
|
||||
|
||||
if (!name || !breed || !sex) {
|
||||
return res.status(400).json({ error: 'Name, breed, and sex are required' });
|
||||
}
|
||||
|
||||
const db = getDatabase();
|
||||
|
||||
// Check if litter_id column exists
|
||||
let hasLitterId = false;
|
||||
try {
|
||||
const columns = db.prepare("PRAGMA table_info(dogs)").all();
|
||||
hasLitterId = columns.some(col => col.name === 'litter_id');
|
||||
} catch (e) {
|
||||
console.error('Error checking schema:', e);
|
||||
}
|
||||
|
||||
// Insert with or without litter_id depending on schema
|
||||
let result;
|
||||
if (hasLitterId) {
|
||||
result = db.prepare(`
|
||||
INSERT INTO dogs (name, registration_number, breed, sex, birth_date, color, microchip, notes, litter_id, photo_urls)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
name,
|
||||
emptyToNull(registration_number),
|
||||
breed,
|
||||
sex,
|
||||
emptyToNull(birth_date),
|
||||
emptyToNull(color),
|
||||
emptyToNull(microchip),
|
||||
emptyToNull(notes),
|
||||
emptyToNull(litter_id),
|
||||
'[]'
|
||||
);
|
||||
} else {
|
||||
result = db.prepare(`
|
||||
INSERT INTO dogs (name, registration_number, breed, sex, birth_date, color, microchip, notes, photo_urls)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
name,
|
||||
emptyToNull(registration_number),
|
||||
breed,
|
||||
sex,
|
||||
emptyToNull(birth_date),
|
||||
emptyToNull(color),
|
||||
emptyToNull(microchip),
|
||||
emptyToNull(notes),
|
||||
'[]'
|
||||
);
|
||||
}
|
||||
// Insert dog (dogs table has NO sire/dam columns)
|
||||
const result = db.prepare(`
|
||||
INSERT INTO dogs (name, registration_number, breed, sex, birth_date, color, microchip, notes, litter_id, photo_urls)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
name,
|
||||
emptyToNull(registration_number),
|
||||
breed,
|
||||
sex,
|
||||
emptyToNull(birth_date),
|
||||
emptyToNull(color),
|
||||
emptyToNull(microchip),
|
||||
emptyToNull(notes),
|
||||
emptyToNull(litter_id),
|
||||
'[]'
|
||||
);
|
||||
|
||||
const dogId = result.lastInsertRowid;
|
||||
console.log(`✓ Dog inserted with ID: ${dogId}`);
|
||||
|
||||
// Add parent relationships
|
||||
if (sire_id) {
|
||||
db.prepare('INSERT INTO parents (dog_id, parent_id, parent_type) VALUES (?, ?, "sire")').run(dogId, sire_id);
|
||||
}
|
||||
if (dam_id) {
|
||||
db.prepare('INSERT INTO parents (dog_id, parent_id, parent_type) VALUES (?, ?, "dam")').run(dogId, dam_id);
|
||||
// Add sire relationship if provided
|
||||
if (sire_id && sire_id !== '' && sire_id !== null) {
|
||||
console.log(` Adding sire relationship: dog ${dogId} -> sire ${sire_id}`);
|
||||
db.prepare('INSERT INTO parents (dog_id, parent_id, parent_type) VALUES (?, ?, ?)').
|
||||
run(dogId, sire_id, 'sire');
|
||||
console.log(` ✓ Sire relationship added`);
|
||||
}
|
||||
|
||||
// Add dam relationship if provided
|
||||
if (dam_id && dam_id !== '' && dam_id !== null) {
|
||||
console.log(` Adding dam relationship: dog ${dogId} -> dam ${dam_id}`);
|
||||
db.prepare('INSERT INTO parents (dog_id, parent_id, parent_type) VALUES (?, ?, ?)').
|
||||
run(dogId, dam_id, 'dam');
|
||||
console.log(` ✓ Dam relationship added`);
|
||||
}
|
||||
|
||||
// Fetch the created dog
|
||||
const dog = db.prepare(`
|
||||
SELECT id, name, registration_number, breed, sex, birth_date,
|
||||
color, microchip, photo_urls, notes, is_active,
|
||||
color, microchip, photo_urls, notes, litter_id, is_active,
|
||||
created_at, updated_at
|
||||
FROM dogs
|
||||
WHERE id = ?
|
||||
`).get(dogId);
|
||||
dog.photo_urls = [];
|
||||
|
||||
console.log(`✓ Dog created successfully: ${dog.name} (ID: ${dogId})`);
|
||||
res.status(201).json(dog);
|
||||
} catch (error) {
|
||||
console.error('Error creating dog:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -190,76 +177,64 @@ router.put('/:id', (req, res) => {
|
||||
try {
|
||||
const { name, registration_number, breed, sex, birth_date, color, microchip, notes, sire_id, dam_id, litter_id } = req.body;
|
||||
|
||||
console.log(`Updating dog ${req.params.id} with data:`, { name, breed, sex, sire_id, dam_id, litter_id });
|
||||
|
||||
const db = getDatabase();
|
||||
|
||||
// Check if litter_id column exists
|
||||
let hasLitterId = false;
|
||||
try {
|
||||
const columns = db.prepare("PRAGMA table_info(dogs)").all();
|
||||
hasLitterId = columns.some(col => col.name === 'litter_id');
|
||||
} catch (e) {
|
||||
console.error('Error checking schema:', e);
|
||||
}
|
||||
// Update dog record (dogs table has NO sire/dam columns)
|
||||
db.prepare(`
|
||||
UPDATE dogs
|
||||
SET name = ?, registration_number = ?, breed = ?, sex = ?,
|
||||
birth_date = ?, color = ?, microchip = ?, notes = ?, litter_id = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
name,
|
||||
emptyToNull(registration_number),
|
||||
breed,
|
||||
sex,
|
||||
emptyToNull(birth_date),
|
||||
emptyToNull(color),
|
||||
emptyToNull(microchip),
|
||||
emptyToNull(notes),
|
||||
emptyToNull(litter_id),
|
||||
req.params.id
|
||||
);
|
||||
console.log(` ✓ Dog record updated`);
|
||||
|
||||
// Update with or without litter_id
|
||||
if (hasLitterId) {
|
||||
db.prepare(`
|
||||
UPDATE dogs
|
||||
SET name = ?, registration_number = ?, breed = ?, sex = ?,
|
||||
birth_date = ?, color = ?, microchip = ?, notes = ?, litter_id = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
name,
|
||||
emptyToNull(registration_number),
|
||||
breed,
|
||||
sex,
|
||||
emptyToNull(birth_date),
|
||||
emptyToNull(color),
|
||||
emptyToNull(microchip),
|
||||
emptyToNull(notes),
|
||||
emptyToNull(litter_id),
|
||||
req.params.id
|
||||
);
|
||||
} else {
|
||||
db.prepare(`
|
||||
UPDATE dogs
|
||||
SET name = ?, registration_number = ?, breed = ?, sex = ?,
|
||||
birth_date = ?, color = ?, microchip = ?, notes = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
name,
|
||||
emptyToNull(registration_number),
|
||||
breed,
|
||||
sex,
|
||||
emptyToNull(birth_date),
|
||||
emptyToNull(color),
|
||||
emptyToNull(microchip),
|
||||
emptyToNull(notes),
|
||||
req.params.id
|
||||
);
|
||||
}
|
||||
|
||||
// Update parent relationships
|
||||
// Remove existing parent relationships
|
||||
db.prepare('DELETE FROM parents WHERE dog_id = ?').run(req.params.id);
|
||||
console.log(` ✓ Old parent relationships removed`);
|
||||
|
||||
if (sire_id) {
|
||||
db.prepare('INSERT INTO parents (dog_id, parent_id, parent_type) VALUES (?, ?, "sire")').run(req.params.id, sire_id);
|
||||
}
|
||||
if (dam_id) {
|
||||
db.prepare('INSERT INTO parents (dog_id, parent_id, parent_type) VALUES (?, ?, "dam")').run(req.params.id, dam_id);
|
||||
// Add new sire relationship if provided
|
||||
if (sire_id && sire_id !== '' && sire_id !== null) {
|
||||
console.log(` Adding sire relationship: dog ${req.params.id} -> sire ${sire_id}`);
|
||||
db.prepare('INSERT INTO parents (dog_id, parent_id, parent_type) VALUES (?, ?, ?)').
|
||||
run(req.params.id, sire_id, 'sire');
|
||||
console.log(` ✓ Sire relationship added`);
|
||||
}
|
||||
|
||||
// Add new dam relationship if provided
|
||||
if (dam_id && dam_id !== '' && dam_id !== null) {
|
||||
console.log(` Adding dam relationship: dog ${req.params.id} -> dam ${dam_id}`);
|
||||
db.prepare('INSERT INTO parents (dog_id, parent_id, parent_type) VALUES (?, ?, ?)').
|
||||
run(req.params.id, dam_id, 'dam');
|
||||
console.log(` ✓ Dam relationship added`);
|
||||
}
|
||||
|
||||
// Fetch updated dog
|
||||
const dog = db.prepare(`
|
||||
SELECT id, name, registration_number, breed, sex, birth_date,
|
||||
color, microchip, photo_urls, notes, is_active,
|
||||
color, microchip, photo_urls, notes, litter_id, is_active,
|
||||
created_at, updated_at
|
||||
FROM dogs
|
||||
WHERE id = ?
|
||||
`).get(req.params.id);
|
||||
dog.photo_urls = dog.photo_urls ? JSON.parse(dog.photo_urls) : [];
|
||||
|
||||
console.log(`✓ Dog updated successfully: ${dog.name} (ID: ${req.params.id})`);
|
||||
res.json(dog);
|
||||
} catch (error) {
|
||||
console.error('Error updating dog:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -269,8 +244,10 @@ router.delete('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
db.prepare('UPDATE dogs SET is_active = 0 WHERE id = ?').run(req.params.id);
|
||||
console.log(`✓ Dog soft-deleted: ID ${req.params.id}`);
|
||||
res.json({ message: 'Dog deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting dog:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -296,6 +273,7 @@ router.post('/:id/photos', upload.single('photo'), (req, res) => {
|
||||
|
||||
res.json({ url: `/uploads/${req.file.filename}`, photos: photoUrls });
|
||||
} catch (error) {
|
||||
console.error('Error uploading photo:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -327,6 +305,7 @@ router.delete('/:id/photos/:photoIndex', (req, res) => {
|
||||
|
||||
res.json({ photos: photoUrls });
|
||||
} catch (error) {
|
||||
console.error('Error deleting photo:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user