mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
[dyad] wrote 4 file(s)
This commit is contained in:
1 parent
5e6670d921
commit
85f191f234
4 files changed
+412
No files matched your search
@@ -0,0 +1,67 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { db, logger } = require('../config/database');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// Rate limiting
|
||||
const loginLimiter = rateLimit({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 10, // 10 requests per minute
|
||||
message: { message: 'Too many login attempts, please try again later' }
|
||||
});
|
||||
|
||||
// Login validation
|
||||
const loginValidation = [
|
||||
body('username').trim().notEmpty().escape(),
|
||||
body('password').trim().notEmpty()
|
||||
];
|
||||
|
||||
// Login route
|
||||
router.post('/login', loginLimiter, loginValidation, async (req, res) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { username, password } = req.body;
|
||||
|
||||
db.get('SELECT * FROM users WHERE username = ?', [username], async (err, user) => {
|
||||
if (err) {
|
||||
logger.error('Login error:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(password, user.password_hash);
|
||||
if (!validPassword) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user.id, username: user.username, is_admin: user.is_admin },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
res.json({ token, user: { id: user.id, username: user.username, is_admin: user.is_admin } });
|
||||
});
|
||||
});
|
||||
|
||||
// Get current user
|
||||
router.get('/me', authMiddleware, (req, res) => {
|
||||
res.json(req.user);
|
||||
});
|
||||
|
||||
// Logout (client-side only, just for completeness)
|
||||
router.post('/logout', (req, res) => {
|
||||
res.json({ message: 'Logged out successfully' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,204 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { db, logger } = require('../config/database');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// Configure multer for image uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: 'public/uploads/',
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueName = `${uuidv4()}${path.extname(file.originalname)}`;
|
||||
cb(null, uniqueName);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage,
|
||||
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
|
||||
fileFilter: (req, file, cb) => {
|
||||
const allowedTypes = /jpeg|jpg|png|gif/;
|
||||
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
|
||||
const mimetype = allowedTypes.test(file.mimetype);
|
||||
if (extname && mimetype) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Only image files are allowed'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Note validation
|
||||
const noteValidation = [
|
||||
body('title').trim().notEmpty().escape(),
|
||||
body('content').trim().optional().escape(),
|
||||
body('archived').isBoolean().optional()
|
||||
];
|
||||
|
||||
// Get all notes for user
|
||||
router.get('/', authMiddleware, (req, res) => {
|
||||
const query = `
|
||||
SELECT n.*,
|
||||
GROUP_CONCAT(t.id || ':' || t.text || ':' || t.completed) as todos,
|
||||
GROUP_CONCAT(i.id || ':' || i.filename) as images
|
||||
FROM notes n
|
||||
LEFT JOIN todos t ON n.id = t.note_id
|
||||
LEFT JOIN images i ON n.id = i.note_id
|
||||
WHERE n.user_id = ?
|
||||
GROUP BY n.id
|
||||
ORDER BY n.created_at DESC
|
||||
`;
|
||||
|
||||
db.all(query, [req.user.id], (err, notes) => {
|
||||
if (err) {
|
||||
logger.error('Error fetching notes:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
|
||||
// Process the results to format todos and images
|
||||
const processedNotes = notes.map(note => ({
|
||||
...note,
|
||||
todos: note.todos ? note.todos.split(',').map(todo => {
|
||||
const [id, text, completed] = todo.split(':');
|
||||
return { id, text, completed: completed === '1' };
|
||||
}) : [],
|
||||
images: note.images ? note.images.split(',').map(image => {
|
||||
const [id, filename] = image.split(':');
|
||||
return { id, filename };
|
||||
}) : []
|
||||
}));
|
||||
|
||||
res.json(processedNotes);
|
||||
});
|
||||
});
|
||||
|
||||
// Create note
|
||||
router.post('/', authMiddleware, noteValidation, (req, res) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { title, content, todos = [] } = req.body;
|
||||
|
||||
db.run(
|
||||
'INSERT INTO notes (user_id, title, content) VALUES (?, ?, ?)',
|
||||
[req.user.id, title, content],
|
||||
function(err) {
|
||||
if (err) {
|
||||
logger.error('Error creating note:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
|
||||
const noteId = this.lastID;
|
||||
|
||||
// Insert todos if any
|
||||
if (todos.length > 0) {
|
||||
const todoValues = todos.map((todo, index) =>
|
||||
`(${noteId}, '${todo.text}', ${todo.completed ? 1 : 0}, ${index})`
|
||||
).join(',');
|
||||
|
||||
db.run(`INSERT INTO todos (note_id, text, completed, position) VALUES ${todoValues}`);
|
||||
}
|
||||
|
||||
res.status(201).json({ id: noteId, title, content, todos: [] });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Update note
|
||||
router.put('/:id', authMiddleware, noteValidation, (req, res) => {
|
||||
const noteId = req.params.id;
|
||||
const { title, content, archived, todos = [] } = req.body;
|
||||
|
||||
db.run(
|
||||
'UPDATE notes SET title = ?, content = ?, archived = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND user_id = ?',
|
||||
[title, content, archived ? 1 : 0, noteId, req.user.id],
|
||||
function(err) {
|
||||
if (err) {
|
||||
logger.error('Error updating note:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
|
||||
// Update todos
|
||||
db.run('DELETE FROM todos WHERE note_id = ?', [noteId], (err) => {
|
||||
if (err) {
|
||||
logger.error('Error deleting todos:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (todos.length > 0) {
|
||||
const todoValues = todos.map((todo, index) =>
|
||||
`(${noteId}, '${todo.text}', ${todo.completed ? 1 : 0}, ${index})`
|
||||
).join(',');
|
||||
|
||||
db.run(`INSERT INTO todos (note_id, text, completed, position) VALUES ${todoValues}`);
|
||||
}
|
||||
});
|
||||
|
||||
res.json({ message: 'Note updated successfully' });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Delete note
|
||||
router.delete('/:id', authMiddleware, (req, res) => {
|
||||
const noteId = req.params.id;
|
||||
|
||||
db.run('DELETE FROM notes WHERE id = ? AND user_id = ?', [noteId, req.user.id], (err) => {
|
||||
if (err) {
|
||||
logger.error('Error deleting note:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
|
||||
// Cleanup related records
|
||||
db.run('DELETE FROM todos WHERE note_id = ?', [noteId]);
|
||||
db.run('DELETE FROM images WHERE note_id = ?', [noteId]);
|
||||
|
||||
res.json({ message: 'Note deleted successfully' });
|
||||
});
|
||||
});
|
||||
|
||||
// Upload image
|
||||
router.post('/:id/images', authMiddleware, upload.single('image'), (req, res) => {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ message: 'No image file provided' });
|
||||
}
|
||||
|
||||
const noteId = req.params.id;
|
||||
const filename = req.file.filename;
|
||||
|
||||
db.run(
|
||||
'INSERT INTO images (note_id, filename) VALUES (?, ?)',
|
||||
[noteId, filename],
|
||||
function(err) {
|
||||
if (err) {
|
||||
logger.error('Error saving image record:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
res.status(201).json({ id: this.lastID, filename });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Delete image
|
||||
router.delete('/:noteId/images/:imageId', authMiddleware, (req, res) => {
|
||||
const { noteId, imageId } = req.params;
|
||||
|
||||
db.run(
|
||||
'DELETE FROM images WHERE id = ? AND note_id = ?',
|
||||
[imageId, noteId],
|
||||
(err) => {
|
||||
if (err) {
|
||||
logger.error('Error deleting image:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
res.json({ message: 'Image deleted successfully' });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,102 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const bcrypt = require('bcrypt');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { db, logger } = require('../config/database');
|
||||
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
||||
|
||||
// User validation rules
|
||||
const userValidation = [
|
||||
body('username').trim().isLength({ min: 3 }).escape(),
|
||||
body('password').isLength({ min: 6 }),
|
||||
body('is_admin').isBoolean().optional()
|
||||
];
|
||||
|
||||
// Get all users (admin only)
|
||||
router.get('/', authMiddleware, adminMiddleware, (req, res) => {
|
||||
db.all('SELECT id, username, is_admin, created_at FROM users', (err, users) => {
|
||||
if (err) {
|
||||
logger.error('Error fetching users:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
res.json(users);
|
||||
});
|
||||
});
|
||||
|
||||
// Create user (admin only)
|
||||
router.post('/', authMiddleware, adminMiddleware, userValidation, async (req, res) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { username, password, is_admin } = req.body;
|
||||
|
||||
try {
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
db.run(
|
||||
'INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?)',
|
||||
[username, hash, is_admin ? 1 : 0],
|
||||
function(err) {
|
||||
if (err) {
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
return res.status(400).json({ message: 'Username already exists' });
|
||||
}
|
||||
logger.error('Error creating user:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
res.status(201).json({ id: this.lastID, username, is_admin });
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error('Password hashing error:', err);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update user (admin only)
|
||||
router.put('/:id', authMiddleware, adminMiddleware, async (req, res) => {
|
||||
const userId = req.params.id;
|
||||
const { password, is_admin } = req.body;
|
||||
|
||||
// Prevent modifying the main admin user
|
||||
if (userId === '1') {
|
||||
return res.status(403).json({ message: 'Cannot modify main admin user' });
|
||||
}
|
||||
|
||||
try {
|
||||
if (password) {
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
db.run('UPDATE users SET password_hash = ? WHERE id = ?', [hash, userId]);
|
||||
}
|
||||
|
||||
if (typeof is_admin !== 'undefined') {
|
||||
db.run('UPDATE users SET is_admin = ? WHERE id = ?', [is_admin ? 1 : 0, userId]);
|
||||
}
|
||||
|
||||
res.json({ message: 'User updated successfully' });
|
||||
} catch (err) {
|
||||
logger.error('Error updating user:', err);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete user (admin only)
|
||||
router.delete('/:id', authMiddleware, adminMiddleware, (req, res) => {
|
||||
const userId = req.params.id;
|
||||
|
||||
// Prevent deleting the main admin user
|
||||
if (userId === '1') {
|
||||
return res.status(403).json({ message: 'Cannot delete main admin user' });
|
||||
}
|
||||
|
||||
db.run('DELETE FROM users WHERE id = ?', [userId], (err) => {
|
||||
if (err) {
|
||||
logger.error('Error deleting user:', err);
|
||||
return res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
res.json({ message: 'User deleted successfully' });
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,39 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const path = require('path');
|
||||
const { logger } = require('./config/database');
|
||||
|
||||
// Create Express app
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
app.use(helmet());
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Routes
|
||||
app.use('/api/auth', require('./routes/auth.routes'));
|
||||
app.use('/api/users', require('./routes/users.routes'));
|
||||
app.use('/api/notes', require('./routes/notes.routes'));
|
||||
|
||||
// Serve static files
|
||||
app.use('/uploads', express.static(path.join(__dirname, 'public/uploads')));
|
||||
|
||||
// SPA fallback
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public/index.html'));
|
||||
});
|
||||
|
||||
// Error handling
|
||||
app.use((err, req, res, next) => {
|
||||
logger.error('Unhandled error:', err);
|
||||
res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
|
||||
// Start server
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => {
|
||||
logger.info(`Server running on port ${PORT}`);
|
||||
});
|
||||
Reference in new issue
Block a user