diff --git a/Dockerfile b/Dockerfile index 25db863..17a0597 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,14 @@ RUN npm ci COPY . . RUN npm run build -FROM nginx:alpine -COPY --from=build /app/build /usr/share/nginx/html -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --omit=dev +COPY server.js ./ +COPY --from=build /app/build ./build + +ENV DATA_DIR=/data +VOLUME ["/data"] +EXPOSE 4000 +CMD ["node", "server.js"] diff --git a/docker-compose.yml b/docker-compose.yml index 6a89a1e..744a090 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,4 +2,9 @@ services: prayer: build: . ports: - - "3000:80" + - "3000:4000" + volumes: + - prayer-data:/data + +volumes: + prayer-data: diff --git a/server.js b/server.js new file mode 100644 index 0000000..6284264 --- /dev/null +++ b/server.js @@ -0,0 +1,62 @@ +const express = require('express'); +const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); + +const app = express(); +const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, 'data'); +const DATA_FILE = path.join(DATA_DIR, 'prayers.json'); +const PORT = process.env.PORT || 4000; + +app.use(cors()); +app.use(express.json()); +app.use(express.static(path.join(__dirname, 'build'))); + +function readPrayers() { + if (!fs.existsSync(DATA_FILE)) return []; + try { + return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); + } catch { + return []; + } +} + +function writePrayers(prayers) { + fs.mkdirSync(DATA_DIR, { recursive: true }); + fs.writeFileSync(DATA_FILE, JSON.stringify(prayers, null, 2)); +} + +app.get('/api/prayers', (req, res) => { + res.json(readPrayers()); +}); + +app.post('/api/prayers', (req, res) => { + const prayers = readPrayers(); + const prayer = req.body; + prayers.unshift(prayer); + writePrayers(prayers); + res.status(201).json(prayer); +}); + +app.put('/api/prayers/:id', (req, res) => { + const prayers = readPrayers(); + const idx = prayers.findIndex(p => p.id === req.params.id); + if (idx === -1) return res.status(404).json({ error: 'Not found' }); + prayers[idx] = { ...prayers[idx], ...req.body }; + writePrayers(prayers); + res.json(prayers[idx]); +}); + +app.delete('/api/prayers/:id', (req, res) => { + const prayers = readPrayers(); + const filtered = prayers.filter(p => p.id !== req.params.id); + writePrayers(filtered); + res.status(204).end(); +}); + +// Serve React for all other routes +app.get('*', (req, res) => { + res.sendFile(path.join(__dirname, 'build', 'index.html')); +}); + +app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); diff --git a/src/App.css b/src/App.css index 34e15bb..3fad4a4 100644 --- a/src/App.css +++ b/src/App.css @@ -234,6 +234,13 @@ body { gap: 0.75rem; } +.loading { + text-align: center; + color: var(--text-muted); + font-style: italic; + padding: 1rem 0; +} + .empty { text-align: center; color: var(--text-muted); diff --git a/src/App.tsx b/src/App.tsx index 781f32e..633bc0f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ type Filter = 'active' | 'answered' | 'all'; export default function App() { const { prayers, + loading, addPrayer, updateAnswer, markAnswered, @@ -111,6 +112,8 @@ export default function App() { + {loading &&

Loading prayers…

} +