first commit
CI / lint-and-test (push) Canceled after 0s

This commit is contained in:
Alberto-Audrix committed 2026-07-28 08:55:05 +07:00
commit 32a36cceff
444 files changed
+67186

No files matched your search

+635
View File
@@ -0,0 +1,635 @@
# 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**