All checks were successful
Deploy to dev.bl.pixeldev.eu / deploy (push) Successful in 5s
- schema.sql: card_text table (single-row, id=1) with content + updated_at
- server.js: Express + pg server with CRUD API:
GET /api/text — read current text
PUT /api/text — create / update text
DELETE /api/text — reset to default lorem ipsum
- package.json: express + pg dependencies (converts project to Node type)
- index.html: card now loads text from DB, inline Edit / Save / Cancel
controls, Reset button with confirm dialog, last-updated timestamp
DB: testapp (appuser) on localhost:5432
Closes #8
93 lines
4.3 KiB
JavaScript
93 lines
4.3 KiB
JavaScript
const express = require('express');
|
|
const { Pool } = require('pg');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
const PORT = parseInt(process.env.PORT || '3070');
|
|
const BASE = (process.env.BASE_PATH || '/test').replace(/\/$/, '');
|
|
|
|
const DEFAULT_TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.';
|
|
|
|
// ── Postgres ──────────────────────────────────────────────────────────────
|
|
const pgPass = process.env.PG_PASS ||
|
|
(fs.existsSync('/root/pg-appuser.env')
|
|
? fs.readFileSync('/root/pg-appuser.env', 'utf8').match(/APP_PASS=(.*)/)?.[1]?.trim()
|
|
: null);
|
|
|
|
const pool = new Pool({
|
|
host: '127.0.0.1',
|
|
port: 5432,
|
|
database: process.env.PG_DB || 'testapp',
|
|
user: process.env.PG_USER || 'appuser',
|
|
password: pgPass,
|
|
});
|
|
|
|
async function initDb() {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS card_text (
|
|
id INT PRIMARY KEY DEFAULT 1,
|
|
content TEXT NOT NULL DEFAULT '',
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
INSERT INTO card_text (id, content) VALUES (1, $1)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
`, [DEFAULT_TEXT]);
|
|
}
|
|
|
|
// ── CORS ──────────────────────────────────────────────────────────────────
|
|
app.use((req, res, next) => {
|
|
res.header('Access-Control-Allow-Origin', '*');
|
|
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
res.header('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
|
|
if (req.method === 'OPTIONS') return res.sendStatus(200);
|
|
next();
|
|
});
|
|
|
|
// ── GET /api/text ─────────────────────────────────────────────────────────
|
|
app.get(['/api/text', BASE + '/api/text'], async (req, res) => {
|
|
try {
|
|
const { rows } = await pool.query('SELECT content, updated_at FROM card_text WHERE id = 1');
|
|
res.json(rows[0] || { content: DEFAULT_TEXT, updated_at: null });
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
// ── PUT /api/text — create / update ──────────────────────────────────────
|
|
app.put(['/api/text', BASE + '/api/text'], async (req, res) => {
|
|
const { content } = req.body;
|
|
if (!content?.trim()) return res.status(400).json({ error: 'content required' });
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`INSERT INTO card_text (id, content, updated_at) VALUES (1, $1, NOW())
|
|
ON CONFLICT (id) DO UPDATE SET content = $1, updated_at = NOW()
|
|
RETURNING content, updated_at`,
|
|
[content.trim()]
|
|
);
|
|
res.json(rows[0]);
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
// ── DELETE /api/text — reset to default ──────────────────────────────────
|
|
app.delete(['/api/text', BASE + '/api/text'], async (req, res) => {
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`UPDATE card_text SET content = $1, updated_at = NOW() WHERE id = 1
|
|
RETURNING content, updated_at`,
|
|
[DEFAULT_TEXT]
|
|
);
|
|
res.json(rows[0] || { content: DEFAULT_TEXT });
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
// ── Serve index.html ──────────────────────────────────────────────────────
|
|
app.get([BASE, BASE + '/', BASE + '/index.html'], (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'index.html'));
|
|
});
|
|
|
|
// ── Boot ──────────────────────────────────────────────────────────────────
|
|
initDb()
|
|
.then(() => app.listen(PORT, '127.0.0.1', () => console.log(`test on :${PORT} at ${BASE}`)))
|
|
.catch(err => { console.error('DB init failed:', err); process.exit(1); });
|