{"stack": "node-express", "files": {"index.js": "/**\n * Example Express app for Niblit.ai.\n * This is a minimal bookmark manager with SQLite persistence.\n */\n\nconst express = require('express');\nconst Database = require('better-sqlite3');\nconst path = require('path');\n\nconst app = express();\nconst PORT = process.env.PORT || 8080;\nconst DATABASE_PATH = process.env.DATABASE_PATH || '/data/app.db';\n\n// Middleware\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n\n// Initialize database\nconst db = new Database(DATABASE_PATH);\ndb.exec(`\n    CREATE TABLE IF NOT EXISTS bookmarks (\n        id INTEGER PRIMARY KEY AUTOINCREMENT,\n        title TEXT NOT NULL,\n        url TEXT NOT NULL,\n        created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n    )\n`);\n\n// Prepared statements\nconst getAllBookmarks = db.prepare('SELECT * FROM bookmarks ORDER BY created_at DESC');\nconst getBookmark = db.prepare('SELECT * FROM bookmarks WHERE id = ?');\nconst insertBookmark = db.prepare('INSERT INTO bookmarks (title, url) VALUES (?, ?)');\nconst deleteBookmark = db.prepare('DELETE FROM bookmarks WHERE id = ?');\n\n// HTML template\nconst htmlTemplate = (bookmarks) => `\n<!DOCTYPE html>\n<html>\n<head>\n    <title>Bookmarks - Niblit.ai</title>\n    <style>\n        body { font-family: -apple-system, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }\n        h1 { color: #4F46E5; }\n        .bookmark { padding: 15px; border: 1px solid #e5e7eb; border-radius: 8px; margin-bottom: 10px; }\n        .bookmark a { color: #4F46E5; text-decoration: none; font-weight: 500; }\n        .bookmark a:hover { text-decoration: underline; }\n        .bookmark .url { color: #6b7280; font-size: 14px; word-break: break-all; }\n        .bookmark .delete { color: #ef4444; font-size: 14px; cursor: pointer; float: right; }\n        form { display: flex; flex-direction: column; gap: 10px; margin-bottom: 30px; padding: 20px; background: #f9fafb; border-radius: 8px; }\n        input { padding: 10px; border: 1px solid #d1d5db; border-radius: 4px; }\n        button { padding: 10px 20px; background: #4F46E5; color: white; border: none; border-radius: 4px; cursor: pointer; }\n        button:hover { background: #4338CA; }\n        .footer { margin-top: 40px; color: #9ca3af; font-size: 14px; }\n    </style>\n</head>\n<body>\n    <h1>Bookmarks</h1>\n\n    <form action=\"/bookmarks\" method=\"POST\">\n        <input type=\"text\" name=\"title\" placeholder=\"Title\" required>\n        <input type=\"url\" name=\"url\" placeholder=\"https://example.com\" required>\n        <button type=\"submit\">Add Bookmark</button>\n    </form>\n\n    <div id=\"bookmarks\">\n        ${bookmarks.map(b => `\n            <div class=\"bookmark\">\n                <a href=\"/bookmarks/${b.id}/delete\" class=\"delete\">Delete</a>\n                <a href=\"${b.url}\" target=\"_blank\">${b.title}</a>\n                <div class=\"url\">${b.url}</div>\n            </div>\n        `).join('')}\n        ${bookmarks.length === 0 ? '<p style=\"color: #9ca3af;\">No bookmarks yet. Add your first one!</p>' : ''}\n    </div>\n\n    <div class=\"footer\">\n        Powered by <a href=\"https://niblit.ai\" style=\"color: #4F46E5;\">Niblit.ai</a>\n    </div>\n</body>\n</html>\n`;\n\n// Routes\napp.get('/', (req, res) => {\n    const bookmarks = getAllBookmarks.all();\n    res.send(htmlTemplate(bookmarks));\n});\n\napp.post('/bookmarks', (req, res) => {\n    const { title, url } = req.body;\n    if (title && url) {\n        insertBookmark.run(title, url);\n    }\n    res.redirect('/');\n});\n\napp.get('/bookmarks/:id/delete', (req, res) => {\n    deleteBookmark.run(req.params.id);\n    res.redirect('/');\n});\n\n// API endpoints\napp.get('/api/bookmarks', (req, res) => {\n    const bookmarks = getAllBookmarks.all();\n    res.json(bookmarks);\n});\n\napp.post('/api/bookmarks', (req, res) => {\n    const { title, url } = req.body;\n    if (!title || !url) {\n        return res.status(400).json({ error: 'Title and URL are required' });\n    }\n    const result = insertBookmark.run(title, url);\n    const bookmark = getBookmark.get(result.lastInsertRowid);\n    res.status(201).json(bookmark);\n});\n\napp.delete('/api/bookmarks/:id', (req, res) => {\n    const result = deleteBookmark.run(req.params.id);\n    if (result.changes === 0) {\n        return res.status(404).json({ error: 'Bookmark not found' });\n    }\n    res.json({ message: 'Deleted' });\n});\n\napp.get('/health', (req, res) => {\n    res.json({ status: 'ok' });\n});\n\n// Start server\napp.listen(PORT, () => {\n    console.log(`Server running on port ${PORT}`);\n});\n", "package.json": "{\n  \"name\": \"niblit-bookmarks\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A simple bookmark manager for Niblit.ai\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"node index.js\"\n  },\n  \"dependencies\": {\n    \"better-sqlite3\": \"^9.4.0\",\n    \"express\": \"^4.18.2\"\n  }\n}\n", "Dockerfile": "FROM node:20-slim\n\nWORKDIR /app\n\n# Install build tools for native modules (better-sqlite3)\nRUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*\n\n# Install dependencies first (better caching)\nCOPY package*.json ./\nRUN npm ci --only=production\n\n# Copy application code\nCOPY . .\n\n# Create data directory for persistent storage\nRUN mkdir -p /data\n\n# Environment variables\nENV PORT=8080\nENV NODE_ENV=production\nENV DATABASE_PATH=/data/app.db\n\nEXPOSE 8080\n\n# Start the application\nCMD [\"node\", \"index.js\"]\n"}}