mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
[dyad] wrote 5 file(s)
This commit is contained in:
1 parent
33b241f0c1
commit
5e6670d921
5 files changed
+218
-83
No files matched your search
+22
@@ -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" ]
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
# }
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user