- React + TipTap editor with formatting toolbar (bold, italic, underline, strikethrough, alignment, highlight, scene breaks) - Custom image node view with resize and alignment controls; server-side WebP conversion via sharp - Express + SQLite backend with JWT auth and admin user management - Export to PDF, EPUB, and ODT - Five themes (Midnight, Gothic Night, Enchanted Forest, Aged Manuscript, Neon Noir); Lora body font for readability - Writing streak, daily word goal, milestones, and Ollama writing prompts - Docker Compose setup for self-hosted deployment behind NPMplus Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
38 lines
1.1 KiB
JavaScript
38 lines
1.1 KiB
JavaScript
import { Router } from 'express'
|
|
import bcrypt from 'bcryptjs'
|
|
import db from '../db.js'
|
|
import { adminAuth } from '../middleware/auth.js'
|
|
|
|
const router = Router()
|
|
router.use(adminAuth)
|
|
|
|
router.get('/users', (req, res) => {
|
|
const users = db.prepare(
|
|
'SELECT id, name, email, created_at FROM users ORDER BY created_at DESC'
|
|
).all()
|
|
res.json(users)
|
|
})
|
|
|
|
router.post('/users', (req, res) => {
|
|
const { name, email, password } = req.body || {}
|
|
if (!name || !email || !password)
|
|
return res.status(400).json({ error: 'Name, email, and password are all required' })
|
|
|
|
try {
|
|
const hash = bcrypt.hashSync(password, 10)
|
|
const { lastInsertRowid } = db.prepare(
|
|
'INSERT INTO users (name, email, password) VALUES (?, ?, ?)'
|
|
).run(name, email.toLowerCase(), hash)
|
|
res.json({ id: lastInsertRowid, name, email })
|
|
} catch {
|
|
res.status(400).json({ error: 'That email is already in use' })
|
|
}
|
|
})
|
|
|
|
router.delete('/users/:id', (req, res) => {
|
|
db.prepare('DELETE FROM users WHERE id = ?').run(req.params.id)
|
|
res.json({ ok: true })
|
|
})
|
|
|
|
export default router
|