From 5e6670d9217dd06bc92ede175635e4f3aa442fbf Mon Sep 17 00:00:00 2001 From: "[dyad]" Date: Thu, 6 Nov 2025 12:20:50 +0100 Subject: [PATCH] [dyad] wrote 5 file(s) --- Dockerfile | 22 +++++++++ config/database.js | 107 ++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 35 +++++++++++++++ middleware/auth.js | 28 ++++++++++++ package.json | 109 +++++++++++---------------------------------- 5 files changed, 218 insertions(+), 83 deletions(-) create mode 100644 Dockerfile create mode 100644 config/database.js create mode 100644 docker-compose.yml create mode 100644 middleware/auth.js diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c096a80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine + +# Create app directory +WORKDIR /app + +# Install app dependencies +COPY package*.json ./ +RUN npm install + +# Bundle app source +COPY . . + +# Create uploads directory +RUN mkdir -p public/uploads + +# Create non-root user +RUN addgroup -S appgroup && adduser -S appuser -G appgroup +RUN chown -R appuser:appgroup /app +USER appuser + +EXPOSE 3000 +CMD [ "node", "server.js" ] \ No newline at end of file diff --git a/config/database.js b/config/database.js new file mode 100644 index 0000000..99c7112 --- /dev/null +++ b/config/database.js @@ -0,0 +1,107 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); +const bcrypt = require('bcrypt'); +const winston = require('winston'); + +const logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ), + transports: [ + new winston.transports.File({ filename: 'data/app.log' }), + new winston.transports.Console() + ] +}); + +const dbPath = path.resolve(__dirname, '../data/notes.db'); + +const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + logger.error('Database connection error:', err); + process.exit(1); + } + logger.info('Connected to SQLite database'); + initializeDatabase(); +}); + +function initializeDatabase() { + db.serialize(() => { + // Users table + db.run(`CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + is_admin INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )`); + + // Notes table + db.run(`CREATE TABLE IF NOT EXISTS notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + title TEXT NOT NULL, + content TEXT, + archived INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + )`); + + // Todos table + db.run(`CREATE TABLE IF NOT EXISTS todos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + note_id INTEGER NOT NULL, + text TEXT NOT NULL, + completed INTEGER DEFAULT 0, + position INTEGER NOT NULL, + FOREIGN KEY (note_id) REFERENCES notes(id) + )`); + + // Images table + db.run(`CREATE TABLE IF NOT EXISTS images ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + note_id INTEGER NOT NULL, + filename TEXT NOT NULL, + upload_date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (note_id) REFERENCES notes(id) + )`); + + // Create indexes + db.run('CREATE INDEX IF NOT EXISTS idx_notes_user_id ON notes(user_id)'); + db.run('CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at)'); + db.run('CREATE INDEX IF NOT EXISTS idx_todos_note_id ON todos(note_id)'); + db.run('CREATE INDEX IF NOT EXISTS idx_images_note_id ON images(note_id)'); + + // Check if admin user exists + db.get('SELECT * FROM users WHERE username = ?', ['admin'], (err, row) => { + if (err) { + logger.error('Error checking admin user:', err); + return; + } + if (!row) { + // Create default admin user + bcrypt.hash('admin', 10, (err, hash) => { + if (err) { + logger.error('Error hashing password:', err); + return; + } + db.run( + 'INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, 1)', + ['admin', hash], + (err) => { + if (err) { + logger.error('Error creating admin user:', err); + return; + } + logger.info('Default admin user created'); + } + ); + }); + } + }); + }); +} + +module.exports = { db, logger }; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..be3e014 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3.8' + +services: + notes-app: + build: . + container_name: notes-todo-app + ports: + - "3000:3000" + volumes: + - ./data:/app/data + - ./public/uploads:/app/public/uploads + environment: + - NODE_ENV=production + - JWT_SECRET=changeme_in_production + - PORT=3000 + restart: unless-stopped + networks: + - nginx_default + +networks: + nginx_default: + external: true + +# Nginx configuration example: +# location /notes/ { +# proxy_pass http://notes-todo-app:3000/; +# proxy_http_version 1.1; +# proxy_set_header Upgrade $http_upgrade; +# proxy_set_header Connection 'upgrade'; +# proxy_set_header Host $host; +# proxy_set_header X-Real-IP $remote_addr; +# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +# proxy_cache_bypass $http_upgrade; +# client_max_body_size 10M; +# } \ No newline at end of file diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..7923741 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,28 @@ +const jwt = require('jsonwebtoken'); +const { logger } = require('../config/database'); + +const authMiddleware = (req, res, next) => { + try { + const token = req.headers.authorization?.split(' ')[1]; + + if (!token) { + return res.status(401).json({ message: 'Authentication required' }); + } + + const decoded = jwt.verify(token, process.env.JWT_SECRET); + req.user = decoded; + next(); + } catch (error) { + logger.error('Auth middleware error:', error); + return res.status(401).json({ message: 'Invalid token' }); + } +}; + +const adminMiddleware = (req, res, next) => { + if (!req.user.is_admin) { + return res.status(403).json({ message: 'Admin access required' }); + } + next(); +}; + +module.exports = { authMiddleware, adminMiddleware }; \ No newline at end of file diff --git a/package.json b/package.json index abd7ce6..758c040 100644 --- a/package.json +++ b/package.json @@ -1,83 +1,26 @@ -{ - "name": "vite_react_shadcn_ts", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "build:dev": "vite build --mode development", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@hookform/resolvers": "^3.9.0", - "@radix-ui/react-accordion": "^1.2.0", - "@radix-ui/react-alert-dialog": "^1.1.1", - "@radix-ui/react-aspect-ratio": "^1.1.0", - "@radix-ui/react-avatar": "^1.1.0", - "@radix-ui/react-checkbox": "^1.1.1", - "@radix-ui/react-collapsible": "^1.1.0", - "@radix-ui/react-context-menu": "^2.2.1", - "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-dropdown-menu": "^2.1.1", - "@radix-ui/react-hover-card": "^1.1.1", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-menubar": "^1.1.1", - "@radix-ui/react-navigation-menu": "^1.2.0", - "@radix-ui/react-popover": "^1.1.1", - "@radix-ui/react-progress": "^1.1.0", - "@radix-ui/react-radio-group": "^1.2.0", - "@radix-ui/react-scroll-area": "^1.1.0", - "@radix-ui/react-select": "^2.1.1", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slider": "^1.2.0", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-switch": "^1.1.0", - "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toast": "^1.2.1", - "@radix-ui/react-toggle": "^1.1.0", - "@radix-ui/react-toggle-group": "^1.1.0", - "@radix-ui/react-tooltip": "^1.1.4", - "@tanstack/react-query": "^5.56.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.0.0", - "date-fns": "^3.6.0", - "embla-carousel-react": "^8.3.0", - "input-otp": "^1.2.4", - "lucide-react": "^0.462.0", - "next-themes": "^0.3.0", - "react": "^18.3.1", - "react-day-picker": "^8.10.1", - "react-dom": "^18.3.1", - "react-hook-form": "^7.53.0", - "react-resizable-panels": "^2.1.3", - "react-router-dom": "^6.26.2", - "recharts": "^2.12.7", - "sonner": "^1.5.0", - "tailwind-merge": "^2.5.2", - "tailwindcss-animate": "^1.0.7", - "vaul": "^0.9.3", - "zod": "^3.23.8" - }, - "devDependencies": { - "@dyad-sh/react-vite-component-tagger": "^0.8.0", - "@eslint/js": "^9.9.0", - "@tailwindcss/typography": "^0.5.15", - "@types/node": "^22.5.5", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react-swc": "^3.9.0", - "autoprefixer": "^10.4.20", - "eslint": "^9.9.0", - "eslint-plugin-react-hooks": "^5.1.0-rc.0", - "eslint-plugin-react-refresh": "^0.4.9", - "globals": "^15.9.0", - "postcss": "^8.4.47", - "tailwindcss": "^3.4.11", - "typescript": "^5.5.3", - "typescript-eslint": "^8.0.1", - "vite": "^6.3.4" - } -} +{ + "name": "notes-todo-app", + "version": "1.0.0", + "description": "Notes and Todo List Application", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js" + }, + "dependencies": { + "bcrypt": "^5.1.1", + "cors": "^2.8.5", + "express": "^4.18.2", + "express-rate-limit": "^7.1.5", + "express-validator": "^7.0.1", + "helmet": "^7.1.0", + "jsonwebtoken": "^9.0.2", + "multer": "^1.4.5-lts.1", + "sqlite3": "^5.1.7", + "uuid": "^9.0.1", + "winston": "^3.11.0" + }, + "devDependencies": { + "nodemon": "^3.0.3" + } +} \ No newline at end of file