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",
|
"name": "notes-todo-app",
|
||||||
"private": true,
|
"version": "1.0.0",
|
||||||
"version": "0.0.0",
|
"description": "Notes and Todo List Application",
|
||||||
"type": "module",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"start": "node server.js",
|
||||||
"build": "vite build",
|
"dev": "nodemon server.js"
|
||||||
"build:dev": "vite build --mode development",
|
},
|
||||||
"lint": "eslint .",
|
"dependencies": {
|
||||||
"preview": "vite preview"
|
"bcrypt": "^5.1.1",
|
||||||
},
|
"cors": "^2.8.5",
|
||||||
"dependencies": {
|
"express": "^4.18.2",
|
||||||
"@hookform/resolvers": "^3.9.0",
|
"express-rate-limit": "^7.1.5",
|
||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"express-validator": "^7.0.1",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
"helmet": "^7.1.0",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"@radix-ui/react-avatar": "^1.1.0",
|
"multer": "^1.4.5-lts.1",
|
||||||
"@radix-ui/react-checkbox": "^1.1.1",
|
"sqlite3": "^5.1.7",
|
||||||
"@radix-ui/react-collapsible": "^1.1.0",
|
"uuid": "^9.0.1",
|
||||||
"@radix-ui/react-context-menu": "^2.2.1",
|
"winston": "^3.11.0"
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
},
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"devDependencies": {
|
||||||
"@radix-ui/react-hover-card": "^1.1.1",
|
"nodemon": "^3.0.3"
|
||||||
"@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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user