write/server/routes/images.js
chris 1f51504de9 Add story writer app with editor, auth, export, and polish features
- 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>
2026-05-11 11:47:55 -04:00

45 lines
1.4 KiB
JavaScript

import { Router } from 'express'
import multer from 'multer'
import sharp from 'sharp'
import path from 'path'
import { fileURLToPath } from 'url'
import { mkdirSync } from 'fs'
import db from '../db.js'
import { auth } from '../middleware/auth.js'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const uploadDir = path.join(__dirname, '..', 'uploads')
mkdirSync(uploadDir, { recursive: true })
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 20 * 1024 * 1024 },
fileFilter: (req, file, cb) =>
file.mimetype.startsWith('image/') ? cb(null, true) : cb(new Error('Images only')),
})
const router = Router()
router.use(auth)
router.post('/', upload.single('file'), async (req, res) => {
if (!req.file) return res.status(400).json({ error: 'No file uploaded' })
const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}.webp`
const outputPath = path.join(uploadDir, filename)
try {
await sharp(req.file.buffer)
.resize(2400, 2400, { fit: 'inside', withoutEnlargement: true })
.webp({ quality: 88 })
.toFile(outputPath)
db.prepare('INSERT INTO images (user_id, filename) VALUES (?, ?)').run(req.user.id, filename)
res.json({ url: `/uploads/${filename}` })
} catch (err) {
console.error('Image processing error:', err)
res.status(500).json({ error: 'Image processing failed' })
}
})
export default router