Add Node.js/Express API service for booking backend
- New api/ service: Express + better-sqlite3, health check at GET /api/health, POST /api/bookings stores sessions with all required fields - Dockerfile with Alpine build deps for native sqlite3 module - docker-compose.yml: api service with volume mounts for data, signed-contracts, contracts - nginx.conf: proxy /api/ to api:3001; Immich regex locations remain higher priority - .gitignore: exclude SQLite db file and signed PDFs from version control Closes #1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
PORT=3001
|
||||
CORS_ORIGIN=http://localhost:8080
|
||||
|
||||
# n8n webhook for email notifications
|
||||
N8N_WEBHOOK_URL=
|
||||
|
||||
# Google Calendar (issue #3)
|
||||
GOOGLE_CALENDAR_ID=
|
||||
GOOGLE_SERVICE_ACCOUNT_JSON=
|
||||
|
||||
# Contract PDF signature placement (issue #6)
|
||||
CONTRACT_SIG_X=100
|
||||
CONTRACT_SIG_Y=700
|
||||
CONTRACT_SIG_PAGE=1
|
||||
|
||||
# OIDC / Authentik (issue #10)
|
||||
SESSION_SECRET=
|
||||
OIDC_ISSUER=
|
||||
OIDC_CLIENT_ID=
|
||||
OIDC_CLIENT_SECRET=
|
||||
OIDC_REDIRECT_URI=
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
RUN apk add --no-cache python3 make g++
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN mkdir -p data signed-contracts contracts
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,31 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const dataDir = path.join(__dirname, 'data');
|
||||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
||||
|
||||
const db = new Database(path.join(dataDir, 'bookings.db'));
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS bookings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
client_name TEXT,
|
||||
client_email TEXT,
|
||||
client_phone TEXT,
|
||||
client_sub TEXT,
|
||||
session_date TEXT,
|
||||
session_type TEXT,
|
||||
session_length TEXT,
|
||||
location TEXT,
|
||||
contract_signed_at TEXT,
|
||||
contract_pdf_path TEXT,
|
||||
payment_status TEXT DEFAULT 'pending',
|
||||
payment_notified_at TEXT,
|
||||
status TEXT DEFAULT 'pending',
|
||||
notes TEXT
|
||||
)
|
||||
`);
|
||||
|
||||
module.exports = db;
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "lisilou-api",
|
||||
"version": "1.0.0",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "node --watch server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^9.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.18.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const db = require('./db');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
app.use(cors({ origin: process.env.CORS_ORIGIN || '*', credentials: true }));
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
app.post('/api/bookings', (req, res) => {
|
||||
const {
|
||||
client_name, client_email, client_phone, client_sub,
|
||||
session_date, session_type, session_length, location,
|
||||
} = req.body;
|
||||
|
||||
if (!client_email || !session_date || !session_type || !session_length) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
const result = db.prepare(`
|
||||
INSERT INTO bookings
|
||||
(client_name, client_email, client_phone, client_sub, session_date, session_type, session_length, location)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(client_name, client_email, client_phone, client_sub, session_date, session_type, session_length, location);
|
||||
|
||||
res.status(201).json({ id: result.lastInsertRowid });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => console.log(`API listening on port ${PORT}`));
|
||||
Reference in New Issue
Block a user