[dyad] wrote 5 file(s)

This commit is contained in:
Michael committed 2025-11-06 12:20:50 +01:00
1 parent 33b241f0c1
commit 5e6670d921
5 files changed
+218 -83

No files matched your search

+22
View File
@@ -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" ]
+107
View File
@@ -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 };
+35
View File
@@ -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;
# }
+28
View File
@@ -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 };
+26 -83
View File
@@ -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"
}
}