agep-cargo/backend/server.js
2025-10-13 05:27:04 +02:00

176 lines
6.5 KiB
JavaScript

import express from 'express'
import cors from 'cors'
import dotenv from 'dotenv'
import { pool } from './db.js'
dotenv.config()
const app = express()
app.use(cors())
app.use(express.json())
// Simple health
app.get('/api/health', (req, res) => res.json({ ok: true }))
// List available cargobikes
app.get('/api/bikes', async (req, res) => {
// fixed bike types
const bikes = [1000, 2000, 3000, 4000, 5000]
res.json({ bikes })
})
// Create a booking (supports single bike_type or multiple bike_types)
app.post('/api/bookings', async (req, res) => {
try {
const { bike_type, bike_types, start_date, end_date, start_time, end_time, name, email } = req.body
const multi = Array.isArray(bike_types) ? bike_types.filter((v) => Number.isFinite(Number(v))).map(Number) : []
const single = Number.isFinite(Number(bike_type)) ? Number(bike_type) : null
if ((!single && multi.length === 0) || !start_date || !end_date || !name || !email) {
return res.status(400).json({ error: 'Missing fields' })
}
// Explicitly set status to 'pending' to avoid relying solely on DB defaults
const text = `INSERT INTO bookings(bike_type, bike_types, start_date, start_time, end_date, end_time, name, email, status)
VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *`
const values = [single, multi.length ? multi.join(',') : null, start_date, start_time || null, end_date, end_time || null, name, email, 'pending']
const result = await pool.query(text, values)
res.status(201).json({ booking: result.rows[0] })
} catch (err) {
console.error(err)
res.status(500).json({ error: 'Server error' })
}
})
// List bookings (simple)
app.get('/api/bookings', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM bookings ORDER BY created_at DESC LIMIT 100')
res.json({ bookings: result.rows })
} catch (err) {
console.error(err)
res.status(500).json({ error: 'Server error' })
}
})
// Update booking status
app.patch('/api/bookings/:id/status', async (req, res) => {
try {
const id = Number(req.params.id)
if (!Number.isFinite(id)) return res.status(400).json({ error: 'Invalid id' })
const { status } = req.body || {}
const allowed = ['pending', 'accepted', 'refused']
if (!allowed.includes(status)) return res.status(400).json({ error: 'Invalid status' })
// Read current status to enforce immutability once accepted
const current = await pool.query('SELECT status FROM bookings WHERE id = $1', [id])
if (current.rowCount === 0) return res.status(404).json({ error: 'Booking not found' })
const curr = current.rows[0].status
if (curr === 'accepted' && status !== 'accepted') {
return res.status(400).json({ error: 'Accepted bookings cannot change status' })
}
const result = await pool.query('UPDATE bookings SET status = $1 WHERE id = $2 RETURNING *', [status, id])
if (result.rowCount === 0) return res.status(404).json({ error: 'Booking not found' })
res.json({ booking: result.rows[0] })
} catch (err) {
console.error(err)
res.status(500).json({ error: 'Server error' })
}
})
// Update booking fields (non-status)
app.patch('/api/bookings/:id', async (req, res) => {
try {
const id = Number(req.params.id)
if (!Number.isFinite(id)) return res.status(400).json({ error: 'Invalid id' })
const allowed = ['bike_type', 'bike_types', 'start_date', 'start_time', 'end_date', 'end_time', 'name', 'email']
const entries = Object.entries(req.body || {}).filter(([k, v]) => allowed.includes(k))
if (entries.length === 0) return res.status(400).json({ error: 'No valid fields to update' })
// Build dynamic update
const sets = []
const values = []
for (let i = 0; i < entries.length; i++) {
const [k, v] = entries[i]
sets.push(`${k} = $${i + 1}`)
values.push(v)
}
values.push(id)
const sql = `UPDATE bookings SET ${sets.join(', ')} WHERE id = $${values.length} RETURNING *`
const result = await pool.query(sql, values)
if (result.rowCount === 0) return res.status(404).json({ error: 'Booking not found' })
res.json({ booking: result.rows[0] })
} catch (err) {
console.error(err)
res.status(500).json({ error: 'Server error' })
}
})
// Delete a booking by id
app.delete('/api/bookings/:id', async (req, res) => {
try {
const id = Number(req.params.id)
if (!Number.isFinite(id)) return res.status(400).json({ error: 'Invalid id' })
const result = await pool.query('DELETE FROM bookings WHERE id = $1 RETURNING *', [id])
if (result.rowCount === 0) return res.status(404).json({ error: 'Booking not found' })
res.json({ ok: true, booking: result.rows[0] })
} catch (err) {
console.error(err)
res.status(500).json({ error: 'Server error' })
}
})
// --- ensure DB tables exist (auto-migration for dev) ---
async function ensureTables() {
const createSql = `
CREATE TABLE IF NOT EXISTS bookings (
id SERIAL PRIMARY KEY,
bike_type INTEGER,
bike_types TEXT,
start_date DATE NOT NULL,
start_time TIME,
end_date DATE NOT NULL,
end_time TIME,
name TEXT NOT NULL,
email TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
`
try {
await pool.query(createSql)
// Ensure columns and constraints for backward compatibility
await pool.query(`ALTER TABLE bookings ADD COLUMN IF NOT EXISTS start_time TIME;`)
await pool.query(`ALTER TABLE bookings ADD COLUMN IF NOT EXISTS end_time TIME;`)
await pool.query(`ALTER TABLE bookings ADD COLUMN IF NOT EXISTS bike_types TEXT;`)
await pool.query(`ALTER TABLE bookings ADD COLUMN IF NOT EXISTS status TEXT;`)
// Ensure default and not-null for status
await pool.query(`ALTER TABLE bookings ALTER COLUMN status SET DEFAULT 'pending';`)
await pool.query(`UPDATE bookings SET status = 'pending' WHERE status IS NULL;`)
await pool.query(`ALTER TABLE bookings ALTER COLUMN status SET NOT NULL;`)
// bike_type nullable for multi-type support
await pool.query(`ALTER TABLE bookings ALTER COLUMN bike_type DROP NOT NULL;`)
console.log('Database: bookings table is present (created/migrated if needed)')
} catch (err) {
console.warn('Warning: could not ensure bookings table (DB may be unavailable).', err.message)
}
}
const port = process.env.PORT || 3000
// Run migrations (if possible) then start server
;(async () => {
await ensureTables()
app.listen(port, () => console.log(`Backend listening on port ${port}`))
})()