const express = require('express'); const cors = require('cors'); const rateLimit = require('express-rate-limit'); const Database = require('better-sqlite3'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 3001; app.use(cors()); app.use(express.json()); // Apply rate limiting to all API routes const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 300, // limit each IP to 300 requests per windowMs standardHeaders: true, legacyHeaders: false, message: { error: 'リクエストが多すぎます。しばらくしてから再試行してください。' }, }); app.use('/api', apiLimiter); const dbPath = path.join(__dirname, 'data.db'); const db = new Database(dbPath); // Initialize database schema db.exec(` CREATE TABLE IF NOT EXISTS cars ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT DEFAULT '' ); CREATE TABLE IF NOT EXISTS reservations ( id INTEGER PRIMARY KEY AUTOINCREMENT, car_id INTEGER NOT NULL, start_date TEXT NOT NULL, end_date TEXT NOT NULL, customer_name TEXT DEFAULT '', notes TEXT DEFAULT '', FOREIGN KEY (car_id) REFERENCES cars(id) ON DELETE CASCADE ); `); // Seed some initial cars if none exist const carCount = db.prepare('SELECT COUNT(*) as cnt FROM cars').get(); if (carCount.cnt === 0) { const insertCar = db.prepare('INSERT INTO cars (name, description) VALUES (?, ?)'); insertCar.run('代車 A', ''); insertCar.run('代車 B', ''); insertCar.run('代車 C', ''); } // --- Cars API --- app.get('/api/cars', (req, res) => { const cars = db.prepare('SELECT * FROM cars ORDER BY id').all(); res.json(cars); }); app.post('/api/cars', (req, res) => { const { name, description = '' } = req.body; if (!name || !name.trim()) { return res.status(400).json({ error: '車名は必須です' }); } const result = db.prepare('INSERT INTO cars (name, description) VALUES (?, ?)').run(name.trim(), description); const car = db.prepare('SELECT * FROM cars WHERE id = ?').get(result.lastInsertRowid); res.status(201).json(car); }); app.put('/api/cars/:id', (req, res) => { const { name, description } = req.body; if (!name || !name.trim()) { return res.status(400).json({ error: '車名は必須です' }); } const result = db.prepare('UPDATE cars SET name = ?, description = ? WHERE id = ?').run(name.trim(), description ?? '', req.params.id); if (result.changes === 0) { return res.status(404).json({ error: '車が見つかりません' }); } const car = db.prepare('SELECT * FROM cars WHERE id = ?').get(req.params.id); res.json(car); }); app.delete('/api/cars/:id', (req, res) => { const result = db.prepare('DELETE FROM cars WHERE id = ?').run(req.params.id); if (result.changes === 0) { return res.status(404).json({ error: '車が見つかりません' }); } res.json({ success: true }); }); // --- Reservations API --- app.get('/api/reservations', (req, res) => { const reservations = db.prepare('SELECT * FROM reservations ORDER BY start_date').all(); res.json(reservations); }); app.post('/api/reservations', (req, res) => { const { car_id, start_date, end_date, customer_name = '', notes = '' } = req.body; if (!car_id || !start_date || !end_date) { return res.status(400).json({ error: 'car_id, start_date, end_date は必須です' }); } if (start_date > end_date) { return res.status(400).json({ error: '開始日は終了日以前である必要があります' }); } const result = db.prepare( 'INSERT INTO reservations (car_id, start_date, end_date, customer_name, notes) VALUES (?, ?, ?, ?, ?)' ).run(car_id, start_date, end_date, customer_name, notes); const reservation = db.prepare('SELECT * FROM reservations WHERE id = ?').get(result.lastInsertRowid); res.status(201).json(reservation); }); app.put('/api/reservations/:id', (req, res) => { const { car_id, start_date, end_date, customer_name, notes } = req.body; if (!car_id || !start_date || !end_date) { return res.status(400).json({ error: 'car_id, start_date, end_date は必須です' }); } if (start_date > end_date) { return res.status(400).json({ error: '開始日は終了日以前である必要があります' }); } const result = db.prepare( 'UPDATE reservations SET car_id = ?, start_date = ?, end_date = ?, customer_name = ?, notes = ? WHERE id = ?' ).run(car_id, start_date, end_date, customer_name ?? '', notes ?? '', req.params.id); if (result.changes === 0) { return res.status(404).json({ error: '予約が見つかりません' }); } const reservation = db.prepare('SELECT * FROM reservations WHERE id = ?').get(req.params.id); res.json(reservation); }); app.delete('/api/reservations/:id', (req, res) => { const result = db.prepare('DELETE FROM reservations WHERE id = ?').run(req.params.id); if (result.changes === 0) { return res.status(404).json({ error: '予約が見つかりません' }); } res.json({ success: true }); }); app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });