636 lines
13 KiB
Markdown
636 lines
13 KiB
Markdown
# Backend API - Dashboard Solusi AI Peternakan Ayam
|
|
|
|
Backend server dengan Express.js dan PostgreSQL database untuk menyimpan data siklus produksi dan mortalitas ayam.
|
|
|
|
## 📋 Daftar Isi
|
|
|
|
- [Teknologi](#teknologi)
|
|
- [Instalasi](#instalasi)
|
|
- [Cara Menjalankan](#cara-menjalankan)
|
|
- [Struktur File](#struktur-file)
|
|
- [Database](#database)
|
|
- [API Endpoints](#api-endpoints)
|
|
- [Environment Variables](#environment-variables)
|
|
- [Development](#development)
|
|
|
|
## 🛠 Teknologi
|
|
|
|
- **Node.js** v18+
|
|
- **Express.js** - Web framework
|
|
- **pg** - PostgreSQL database driver
|
|
- **cors** - Cross-origin resource sharing
|
|
- **dotenv** - Environment variables
|
|
|
|
## 💿 Instalasi
|
|
|
|
```bash
|
|
cd backend
|
|
npm install
|
|
```
|
|
|
|
## ▶️ Cara Menjalankan
|
|
|
|
### Development Mode
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
Server akan berjalan di http://localhost:5001
|
|
|
|
### Production Mode
|
|
|
|
```bash
|
|
npm start
|
|
```
|
|
|
|
### Seeding Database
|
|
|
|
```bash
|
|
npm run seed
|
|
```
|
|
|
|
Akan mengisi database dengan 4 siklus produksi awal.
|
|
|
|
## 📁 Struktur File
|
|
|
|
```
|
|
backend/
|
|
├── database/
|
|
│ ├── db.js # PostgreSQL connection pool & initialization
|
|
│ ├── schema.sql # PostgreSQL schema definition
|
|
│ ├── seed-postgres.js # Data seeding script
|
|
│ ├── run-migrations.js # Migration runner
|
|
│ └── migrations/ # Database migration files
|
|
├── models/
|
|
│ ├── Cycle.js # Cycle CRUD operations
|
|
│ └── Mortality.js # Mortality CRUD operations
|
|
├── routes/
|
|
│ ├── cycles.js # Cycle API routes
|
|
│ └── mortality.js # Mortality API routes
|
|
├── server.js # Express app entry point
|
|
├── startup.sh # Startup script for Docker
|
|
├── package.json
|
|
├── .env # Environment configuration
|
|
└── README.md
|
|
```
|
|
|
|
## 🗄️ Database
|
|
|
|
### Schema
|
|
|
|
#### Table: cycles
|
|
|
|
```sql
|
|
CREATE TABLE cycles (
|
|
id VARCHAR(50) PRIMARY KEY,
|
|
total_days INTEGER NOT NULL,
|
|
current_day INTEGER NOT NULL,
|
|
start_date DATE NOT NULL,
|
|
end_date DATE,
|
|
chick_in_weight INTEGER,
|
|
doc_in_count INTEGER,
|
|
status VARCHAR(20) NOT NULL CHECK(status IN ('Completed', 'Active', 'Upcoming')),
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
```
|
|
|
|
#### Table: mortality_records
|
|
|
|
```sql
|
|
CREATE TABLE mortality_records (
|
|
id SERIAL PRIMARY KEY,
|
|
cycle_id VARCHAR(50) NOT NULL,
|
|
day INTEGER NOT NULL,
|
|
mortality_count INTEGER NOT NULL DEFAULT 0,
|
|
is_edited BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (cycle_id) REFERENCES cycles(id) ON DELETE CASCADE,
|
|
UNIQUE(cycle_id, day)
|
|
);
|
|
```
|
|
|
|
### Indexes
|
|
|
|
```sql
|
|
CREATE INDEX idx_mortality_cycle_id ON mortality_records(cycle_id);
|
|
CREATE INDEX idx_mortality_day ON mortality_records(day);
|
|
CREATE INDEX idx_cycles_status ON cycles(status);
|
|
CREATE INDEX idx_cycles_start_date ON cycles(start_date);
|
|
```
|
|
|
|
See complete schema in [database/schema.sql](database/schema.sql)
|
|
|
|
### Initial Data
|
|
|
|
Database akan terisi otomatis dengan 4 siklus:
|
|
|
|
| Cycle ID | Status | Start Date | End Date | DOC Count |
|
|
| -------------------- | --------- | ---------- | ---------- | --------- |
|
|
| CYCLE-JBW-2025-05-20 | Completed | 2025-05-20 | 2025-06-30 | 20,000 |
|
|
| CYCLE-JBW-2025-07-11 | Completed | 2025-07-11 | 2025-08-21 | 20,000 |
|
|
| CYCLE-JBW-2025-10-22 | Completed | 2025-10-22 | 2025-12-02 | 20,000 |
|
|
| CYCLE-JBW-2025-12-10 | Active | 2025-12-10 | 2026-01-20 | 20,000 |
|
|
|
|
## 📡 API Endpoints
|
|
|
|
Base URL: `http://localhost:5001`
|
|
|
|
### Health Check
|
|
|
|
```http
|
|
GET /health
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"timestamp": "2025-12-22T03:02:20.793Z"
|
|
}
|
|
```
|
|
|
|
### Cycles API
|
|
|
|
#### Get All Cycles
|
|
|
|
```http
|
|
GET /api/cycles
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"id": "CYCLE-JBW-2025-12-10",
|
|
"totalDays": 42,
|
|
"currentDay": 7,
|
|
"startDate": "2025-12-10",
|
|
"endDate": "2026-01-20",
|
|
"chickInWeight": null,
|
|
"docInCount": 20000,
|
|
"status": "Active",
|
|
"createdAt": "2025-12-22 02:59:17",
|
|
"updatedAt": "2025-12-22 02:59:17"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Get Active Cycle
|
|
|
|
```http
|
|
GET /api/cycles/active
|
|
```
|
|
|
|
#### Get Cycle by ID
|
|
|
|
```http
|
|
GET /api/cycles/:id
|
|
```
|
|
|
|
Example: `GET /api/cycles/CYCLE-JBW-2025-12-10`
|
|
|
|
#### Create Cycle
|
|
|
|
```http
|
|
POST /api/cycles
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"id": "CYCLE-JBW-2025-12-10",
|
|
"totalDays": 42,
|
|
"currentDay": 0,
|
|
"startDate": "2025-12-10",
|
|
"endDate": "2026-01-20",
|
|
"chickInWeight": 42,
|
|
"docInCount": 20000,
|
|
"status": "Active"
|
|
}
|
|
```
|
|
|
|
#### Update Cycle
|
|
|
|
```http
|
|
PUT /api/cycles/:id
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"totalDays": 42,
|
|
"currentDay": 7,
|
|
"startDate": "2025-12-10",
|
|
"endDate": "2026-01-20",
|
|
"chickInWeight": 42,
|
|
"docInCount": 20000,
|
|
"status": "Active"
|
|
}
|
|
```
|
|
|
|
#### Delete Cycle
|
|
|
|
```http
|
|
DELETE /api/cycles/:id
|
|
```
|
|
|
|
### Mortality API
|
|
|
|
#### Get All Mortality Records for Cycle
|
|
|
|
```http
|
|
GET /api/mortality/:cycleId
|
|
```
|
|
|
|
Example: `GET /api/mortality/CYCLE-JBW-2025-12-10`
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"id": 1,
|
|
"cycleId": "CYCLE-JBW-2025-12-10",
|
|
"day": 0,
|
|
"mortalityCount": 25,
|
|
"isEdited": true,
|
|
"createdAt": "2025-12-22 03:00:00",
|
|
"updatedAt": "2025-12-22 03:00:00"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Get Mortality Record for Specific Day
|
|
|
|
```http
|
|
GET /api/mortality/:cycleId/:day
|
|
```
|
|
|
|
Example: `GET /api/mortality/CYCLE-JBW-2025-12-10/5`
|
|
|
|
#### Update/Create Mortality Record
|
|
|
|
```http
|
|
PUT /api/mortality/:cycleId/:day
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"mortalityCount": 30
|
|
}
|
|
```
|
|
|
|
- Creates new record if doesn't exist
|
|
- Updates existing record if exists
|
|
- Sets `is_edited` flag to true
|
|
|
|
#### Delete Mortality Record
|
|
|
|
```http
|
|
DELETE /api/mortality/:cycleId/:day
|
|
```
|
|
|
|
Removes the mortality record, effectively resetting it to default value.
|
|
|
|
## 🔐 Environment Variables
|
|
|
|
File: `.env`
|
|
|
|
```env
|
|
PORT=5001
|
|
NODE_ENV=development
|
|
|
|
# PostgreSQL Database Configuration
|
|
DB_HOST=localhost
|
|
DB_PORT=5432
|
|
DB_USER=dashboard_user
|
|
DB_PASSWORD=your_secure_password_here
|
|
DB_NAME=dashboard_db
|
|
DB_SSL=false
|
|
|
|
# Connection pool settings (optional)
|
|
DB_POOL_MIN=2
|
|
DB_POOL_MAX=10
|
|
```
|
|
|
|
### Variables
|
|
|
|
| Variable | Description | Default |
|
|
| ----------- | ------------------------------------ | -------------- |
|
|
| PORT | Server port | 5001 |
|
|
| NODE_ENV | Environment (development/production) | development |
|
|
| DB_HOST | PostgreSQL host | localhost |
|
|
| DB_PORT | PostgreSQL port | 5432 |
|
|
| DB_USER | PostgreSQL user | dashboard_user |
|
|
| DB_PASSWORD | PostgreSQL password | (required) |
|
|
| DB_NAME | Database name | dashboard_db |
|
|
| DB_SSL | Enable SSL connection | false |
|
|
| DB_POOL_MIN | Minimum pool connections | 2 |
|
|
| DB_POOL_MAX | Maximum pool connections | 10 |
|
|
|
|
## 🔧 Development
|
|
|
|
### Database Helper Functions
|
|
|
|
File: `database/db.js`
|
|
|
|
```javascript
|
|
// Get connection pool and helpers
|
|
const { pool, query, transaction, toISODate, parseISODate } = require('./database/db');
|
|
|
|
// Execute query
|
|
const result = await query('SELECT * FROM cycles WHERE status = $1', ['Active']);
|
|
|
|
// Run transaction
|
|
await transaction(async (client) => {
|
|
await client.query('UPDATE cycles SET current_day = $1 WHERE id = $2', [
|
|
7,
|
|
'CYCLE-JBW-2025-12-10',
|
|
]);
|
|
await client.query('INSERT INTO mortality_records ...');
|
|
});
|
|
|
|
// Date helpers
|
|
toISODate(new Date()); // Converts Date to YYYY-MM-DD
|
|
parseISODate('2025-12-10'); // Converts ISO string to Date
|
|
```
|
|
|
|
### Models
|
|
|
|
#### Cycle Model
|
|
|
|
File: `models/Cycle.js`
|
|
|
|
```javascript
|
|
const Cycle = require('./models/Cycle');
|
|
|
|
// Get all cycles
|
|
Cycle.getAll();
|
|
|
|
// Get cycle by ID
|
|
Cycle.getById('CYCLE-JBW-2025-12-10');
|
|
|
|
// Get active cycle
|
|
Cycle.getActive();
|
|
|
|
// Create new cycle
|
|
Cycle.create({
|
|
id: 'CYCLE-JBW-2025-12-10',
|
|
totalDays: 42,
|
|
currentDay: 0,
|
|
startDate: '2025-12-10',
|
|
endDate: '2026-01-20',
|
|
docInCount: 20000,
|
|
status: 'Active',
|
|
});
|
|
|
|
// Update cycle
|
|
Cycle.update('CYCLE-JBW-2025-12-10', {
|
|
currentDay: 7,
|
|
// ... other fields
|
|
});
|
|
|
|
// Delete cycle
|
|
Cycle.delete('CYCLE-JBW-2025-12-10');
|
|
```
|
|
|
|
#### Mortality Model
|
|
|
|
File: `models/Mortality.js`
|
|
|
|
```javascript
|
|
const Mortality = require('./models/Mortality');
|
|
|
|
// Get all mortality records for a cycle
|
|
Mortality.getByCycle('CYCLE-JBW-2025-12-10');
|
|
|
|
// Get mortality record for specific day
|
|
Mortality.getByDay('CYCLE-JBW-2025-12-10', 5);
|
|
|
|
// Insert or update mortality record
|
|
Mortality.upsert('CYCLE-JBW-2025-12-10', 5, 30, true);
|
|
|
|
// Delete mortality record
|
|
Mortality.delete('CYCLE-JBW-2025-12-10', 5);
|
|
```
|
|
|
|
### Request Logging
|
|
|
|
All requests are logged with timestamp, method, and path:
|
|
|
|
```
|
|
2025-12-22T03:02:20.793Z - GET /health
|
|
2025-12-22T03:02:22.884Z - GET /api/cycles
|
|
2025-12-22T03:02:24.985Z - GET /api/cycles/active
|
|
```
|
|
|
|
SQL queries are also logged in development mode.
|
|
|
|
### CORS Configuration
|
|
|
|
File: `server.js`
|
|
|
|
```javascript
|
|
const corsOptions = {
|
|
origin: ['http://localhost:3001', 'http://localhost:5173'],
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
credentials: true,
|
|
};
|
|
```
|
|
|
|
Add more origins as needed for different environments.
|
|
|
|
## 🧪 Testing API
|
|
|
|
### Using curl
|
|
|
|
```bash
|
|
# Health check
|
|
curl http://localhost:5001/health
|
|
|
|
# Get all cycles
|
|
curl http://localhost:5001/api/cycles
|
|
|
|
# Get active cycle
|
|
curl http://localhost:5001/api/cycles/active
|
|
|
|
# Get mortality records
|
|
curl http://localhost:5001/api/mortality/CYCLE-JBW-2025-12-10
|
|
|
|
# Create mortality record
|
|
curl -X PUT http://localhost:5001/api/mortality/CYCLE-JBW-2025-12-10/5 \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"mortalityCount": 30}'
|
|
|
|
# Delete mortality record
|
|
curl -X DELETE http://localhost:5001/api/mortality/CYCLE-JBW-2025-12-10/5
|
|
```
|
|
|
|
### Using Postman or Thunder Client
|
|
|
|
Import the following collection:
|
|
|
|
```json
|
|
{
|
|
"info": {
|
|
"name": "Dashboard Peternakan API",
|
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
},
|
|
"item": [
|
|
{
|
|
"name": "Health Check",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "http://localhost:5001/health"
|
|
}
|
|
},
|
|
{
|
|
"name": "Get All Cycles",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "http://localhost:5001/api/cycles"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## 📝 Database Maintenance
|
|
|
|
### Backup Database
|
|
|
|
**From Docker (Production):**
|
|
|
|
```bash
|
|
# Run backup script (saves to ./backups/)
|
|
./scripts/backup-postgres.sh
|
|
```
|
|
|
|
**Fetch from Production Server:**
|
|
|
|
```bash
|
|
REMOTE_USER=your-user \
|
|
REMOTE_HOST=your-host \
|
|
REMOTE_PROJECT_DIR=/path/to/project \
|
|
./scripts/fetch-prod-db.sh
|
|
```
|
|
|
|
### Restore Database
|
|
|
|
**Restore to Local:**
|
|
|
|
```bash
|
|
# This will reset local database and restore from backup
|
|
./scripts/restore-postgres-local.sh --reset backups/dashboard_db-YYYYMMDD-HHMMSS.sql.gz
|
|
```
|
|
|
|
### Reset Database
|
|
|
|
**Local Development:**
|
|
|
|
```bash
|
|
npm run seed # Runs seed-postgres.js
|
|
```
|
|
|
|
**Docker:**
|
|
|
|
```bash
|
|
docker-compose down -v # Remove volumes
|
|
docker-compose up -d # Recreate with fresh data
|
|
```
|
|
|
|
### View Database
|
|
|
|
**Using psql CLI:**
|
|
|
|
```bash
|
|
# Connect to local database
|
|
psql -h localhost -p 5432 -U dashboard_user -d dashboard_db
|
|
|
|
# SQL commands
|
|
\dt # Show all tables
|
|
\d cycles # Show table schema
|
|
SELECT * FROM cycles; # Query data
|
|
SELECT * FROM mortality_records;
|
|
\q # Exit
|
|
```
|
|
|
|
**Using Docker:**
|
|
|
|
```bash
|
|
docker-compose exec database psql -U dashboard_user -d dashboard_db
|
|
```
|
|
|
|
**Using DBeaver (GUI):**
|
|
|
|
- Download from https://dbeaver.io/
|
|
- Connect to: localhost:5432 (or 15432 for Docker)
|
|
- Database: dashboard_db
|
|
- User/Password: from .env file
|
|
|
|
## 🚨 Error Handling
|
|
|
|
All endpoints return consistent error format:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "Error message here"
|
|
}
|
|
```
|
|
|
|
HTTP Status Codes:
|
|
|
|
- `200` - Success
|
|
- `201` - Created
|
|
- `400` - Bad Request (invalid input)
|
|
- `404` - Not Found
|
|
- `500` - Internal Server Error
|
|
|
|
## 🔒 Security Notes
|
|
|
|
- Database credentials should be kept in .env (gitignored)
|
|
- Foreign keys enforce referential integrity
|
|
- SQL injection is prevented by using parameterized queries ($1, $2, etc.)
|
|
- CORS is configured for specific origins only
|
|
- Input validation on all endpoints
|
|
- Connection pooling manages database connections efficiently
|
|
- SSL can be enabled for production (set DB_SSL=true)
|
|
|
|
## 📚 Additional Resources
|
|
|
|
- [Express.js Documentation](https://expressjs.com/)
|
|
- [node-postgres (pg) Documentation](https://node-postgres.com/)
|
|
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
|
- [Local Database Setup Guide](../docs/LOCAL_DATABASE_GUIDE.md)
|
|
|
|
## 🤝 Contributing
|
|
|
|
When contributing to the backend:
|
|
|
|
1. Follow existing code structure
|
|
2. Add error handling for new endpoints
|
|
3. Update this README if adding new features
|
|
4. Test all endpoints before committing
|
|
5. Keep models thin - business logic in models, HTTP in routes
|
|
|
|
## 📞 Support
|
|
|
|
For backend-specific issues:
|
|
|
|
- Check server logs in console
|
|
- Verify PostgreSQL is running: `pg_isready` or `docker-compose ps`
|
|
- Test database connection: `npm run seed`
|
|
- Check port availability: `lsof -i :5001`
|
|
- Verify .env configuration (DB_HOST, DB_PORT, credentials)
|
|
|
|
---
|
|
|
|
**Backend developed by PT Cipta Pola Solusi Prima - 2025**
|