Add version history, screenshots, embed generator, collections, npm search, format-on-save, and custom fonts
This commit is contained in:
108
server.js
108
server.js
@@ -1,9 +1,9 @@
|
||||
import express from 'express';
|
||||
import { nanoid } from 'nanoid';
|
||||
import db, { stmts, setFiddleTags } from './db.js';
|
||||
import db, { stmts, setFiddleTags, snapshotVersion } from './db.js';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(express.json({ limit: '2mb' }));
|
||||
|
||||
// HTML routes must be defined before static middleware (which would serve index.html for /)
|
||||
app.get('/', (_req, res) => {
|
||||
@@ -66,7 +66,7 @@ app.get('/api/fiddles', (req, res) => {
|
||||
try {
|
||||
const countRow = db.prepare(`SELECT COUNT(*) as total FROM fiddles f ${where}`).get(params);
|
||||
const fiddles = db.prepare(`
|
||||
SELECT f.id, f.title, f.css_type, f.js_type, f.created_at, f.updated_at,
|
||||
SELECT f.id, f.title, f.css_type, f.js_type, f.created_at, f.updated_at, f.screenshot,
|
||||
SUBSTR(f.html, 1, 200) as html_preview, SUBSTR(f.js, 1, 200) as js_preview
|
||||
FROM fiddles f ${where}
|
||||
ORDER BY ${orderBy}
|
||||
@@ -96,6 +96,10 @@ app.get('/api/fiddles/:id', (req, res) => {
|
||||
app.put('/api/fiddles/:id', (req, res) => {
|
||||
const existing = stmts.get.get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'Not found' });
|
||||
|
||||
// Snapshot current state as a version before overwriting
|
||||
snapshotVersion(req.params.id);
|
||||
|
||||
const {
|
||||
title = existing.title,
|
||||
html = existing.html,
|
||||
@@ -105,9 +109,11 @@ app.put('/api/fiddles/:id', (req, res) => {
|
||||
js_type = existing.js_type || 'javascript',
|
||||
listed = existing.listed,
|
||||
options = existing.options || '{}',
|
||||
screenshot,
|
||||
tags,
|
||||
} = req.body;
|
||||
stmts.update.run({ id: req.params.id, title, html, css, css_type, js, js_type, listed: listed ? 1 : 0, options });
|
||||
if (screenshot !== undefined) stmts.updateScreenshot.run(screenshot, req.params.id);
|
||||
if (Array.isArray(tags)) setFiddleTags(req.params.id, tags);
|
||||
const fiddleTags = stmts.getTagsForFiddle.all(req.params.id);
|
||||
res.json({ id: req.params.id, title, html, css, css_type, js, js_type, listed, options, tags: fiddleTags });
|
||||
@@ -118,5 +124,101 @@ app.get('/api/tags', (_req, res) => {
|
||||
res.json({ tags: stmts.listTags.all() });
|
||||
});
|
||||
|
||||
// ===================== Version History API =====================
|
||||
|
||||
app.get('/api/fiddles/:id/versions', (req, res) => {
|
||||
const fiddle = stmts.get.get(req.params.id);
|
||||
if (!fiddle) return res.status(404).json({ error: 'Not found' });
|
||||
const versions = stmts.listVersions.all(req.params.id);
|
||||
res.json({ versions });
|
||||
});
|
||||
|
||||
app.get('/api/fiddles/:id/versions/:ver', (req, res) => {
|
||||
const ver = parseInt(req.params.ver, 10);
|
||||
const version = stmts.getVersion.get(req.params.id, ver);
|
||||
if (!version) return res.status(404).json({ error: 'Version not found' });
|
||||
res.json(version);
|
||||
});
|
||||
|
||||
app.post('/api/fiddles/:id/revert/:ver', (req, res) => {
|
||||
const existing = stmts.get.get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'Not found' });
|
||||
const ver = parseInt(req.params.ver, 10);
|
||||
const version = stmts.getVersion.get(req.params.id, ver);
|
||||
if (!version) return res.status(404).json({ error: 'Version not found' });
|
||||
|
||||
// Snapshot current state before reverting
|
||||
snapshotVersion(req.params.id);
|
||||
|
||||
stmts.update.run({
|
||||
id: req.params.id,
|
||||
title: existing.title,
|
||||
html: version.html,
|
||||
css: version.css,
|
||||
css_type: version.css_type,
|
||||
js: version.js,
|
||||
js_type: version.js_type,
|
||||
listed: existing.listed,
|
||||
options: version.options || existing.options || '{}',
|
||||
});
|
||||
const updated = stmts.get.get(req.params.id);
|
||||
updated.tags = stmts.getTagsForFiddle.all(req.params.id);
|
||||
res.json(updated);
|
||||
});
|
||||
|
||||
// ===================== Collections API =====================
|
||||
|
||||
app.post('/api/collections', (req, res) => {
|
||||
const id = nanoid(10);
|
||||
const { name = 'Untitled Collection', description = '' } = req.body;
|
||||
try {
|
||||
stmts.insertCollection.run({ id, name, description });
|
||||
res.json({ id, name, description, fiddle_count: 0 });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/collections', (_req, res) => {
|
||||
res.json({ collections: stmts.listCollections.all() });
|
||||
});
|
||||
|
||||
app.get('/api/collections/:id', (req, res) => {
|
||||
const col = stmts.getCollection.get(req.params.id);
|
||||
if (!col) return res.status(404).json({ error: 'Not found' });
|
||||
const fiddles = stmts.getCollectionFiddles.all(req.params.id);
|
||||
for (const f of fiddles) f.tags = stmts.getTagsForFiddle.all(f.id);
|
||||
res.json({ ...col, fiddles });
|
||||
});
|
||||
|
||||
app.put('/api/collections/:id', (req, res) => {
|
||||
const col = stmts.getCollection.get(req.params.id);
|
||||
if (!col) return res.status(404).json({ error: 'Not found' });
|
||||
const { name = col.name, description = col.description } = req.body;
|
||||
stmts.updateCollection.run({ id: req.params.id, name, description });
|
||||
res.json({ id: req.params.id, name, description });
|
||||
});
|
||||
|
||||
app.delete('/api/collections/:id', (req, res) => {
|
||||
const col = stmts.getCollection.get(req.params.id);
|
||||
if (!col) return res.status(404).json({ error: 'Not found' });
|
||||
stmts.deleteCollection.run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
app.post('/api/collections/:id/fiddles', (req, res) => {
|
||||
const col = stmts.getCollection.get(req.params.id);
|
||||
if (!col) return res.status(404).json({ error: 'Collection not found' });
|
||||
const { fiddle_id } = req.body;
|
||||
if (!fiddle_id) return res.status(400).json({ error: 'fiddle_id required' });
|
||||
stmts.addFiddleToCollection.run({ collection_id: req.params.id, fiddle_id });
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
app.delete('/api/collections/:id/fiddles/:fid', (req, res) => {
|
||||
stmts.removeFiddleFromCollection.run(req.params.id, req.params.fid);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
app.listen(port, () => console.log(`Fiddle server running on http://localhost:${port}`));
|
||||
|
||||
Reference in New Issue
Block a user