- Full-stack: React 18 + Express + SQLite - Drag-and-drop kanban boards with @hello-pangea/dnd - Google App Password email integration (SMTP + IMAP) - Inbound email: create cards by sending emails - Reply-to-card: email replies become comments - Admin/user management with role-based access - Setup wizard: email config → admin creation - Checklists, time tracking, priorities, labels, due dates - Real-time notifications with activity feed - Beautiful HTML email templates
78 lines
3.9 KiB
JavaScript
78 lines
3.9 KiB
JavaScript
import { Router } from 'express';
|
|
import bcrypt from 'bcryptjs';
|
|
import db from '../db.js';
|
|
import { authMiddleware, adminOnly, generateToken } from '../middleware/auth.js';
|
|
import { sendMail, buildCardNotificationHtml, buildCardUrl } from '../email/transporter.js';
|
|
|
|
const router = Router();
|
|
|
|
router.use(authMiddleware);
|
|
|
|
router.get('/', (req, res) => {
|
|
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admin only' });
|
|
const users = db.prepare('SELECT id, email, name, avatar_color, role, is_active, created_at FROM users ORDER BY created_at').all();
|
|
res.json(users);
|
|
});
|
|
|
|
router.post('/', adminOnly, async (req, res) => {
|
|
const { email, name, password, role } = req.body;
|
|
if (!email || !name || !password) return res.status(400).json({ error: 'All fields required' });
|
|
if (password.length < 6) return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
|
const existing = db.prepare('SELECT id FROM users WHERE email = ?').get(email);
|
|
if (existing) return res.status(400).json({ error: 'Email already exists' });
|
|
const hash = await bcrypt.hash(password, 12);
|
|
const colors = ['#ef4444','#f97316','#eab308','#22c55e','#14b8a6','#3b82f6','#6366f1','#a855f7','#ec4899'];
|
|
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
const result = db.prepare('INSERT INTO users (email, name, password, role, avatar_color) VALUES (?, ?, ?, ?, ?)')
|
|
.run(email, name, hash, role || 'member', color);
|
|
const user = db.prepare('SELECT id, email, name, avatar_color, role FROM users WHERE id = ?').get(result.lastInsertRowid);
|
|
try {
|
|
await sendMail({
|
|
to: email,
|
|
subject: 'Welcome to TeamFlow! 🚀',
|
|
html: `<div style="padding:32px;font-family:sans-serif;max-width:480px;margin:0 auto">
|
|
<h2 style="color:#6366f1">Welcome to TeamFlow, ${name}!</h2>
|
|
<p>Your account has been created. Log in to get started.</p>
|
|
<p><strong>Email:</strong> ${email}</p>
|
|
<p><strong>Password:</strong> (set by your admin)</p>
|
|
<a href="${process.env.APP_URL || 'http://localhost:5173'}" style="display:inline-block;background:#6366f1;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;margin-top:16px">Go to TeamFlow</a>
|
|
</div>`,
|
|
});
|
|
} catch {}
|
|
res.json(user);
|
|
});
|
|
|
|
router.put('/:id', adminOnly, (req, res) => {
|
|
const { name, role, is_active } = req.body;
|
|
const user = db.prepare('SELECT id FROM users WHERE id = ?').get(req.params.id);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
if (user.id === req.user.id) return res.status(400).json({ error: 'Cannot modify yourself' });
|
|
if (name !== undefined) db.prepare('UPDATE users SET name = ? WHERE id = ?').run(name, user.id);
|
|
if (role !== undefined) db.prepare('UPDATE users SET role = ? WHERE id = ?').run(role, user.id);
|
|
if (is_active !== undefined) db.prepare('UPDATE users SET is_active = ? WHERE id = ?').run(is_active ? 1 : 0, user.id);
|
|
const updated = db.prepare('SELECT id, email, name, avatar_color, role, is_active FROM users WHERE id = ?').get(user.id);
|
|
res.json(updated);
|
|
});
|
|
|
|
router.put('/:id/reset-password', adminOnly, async (req, res) => {
|
|
const { password } = req.body;
|
|
if (!password || password.length < 6) return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
|
const user = db.prepare('SELECT id, email, name FROM users WHERE id = ?').get(req.params.id);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
const hash = await bcrypt.hash(password, 12);
|
|
db.prepare('UPDATE users SET password = ? WHERE id = ?').run(hash, user.id);
|
|
res.json({ success: true });
|
|
});
|
|
|
|
router.get('/board/:boardId', (req, res) => {
|
|
const members = db.prepare(`
|
|
SELECT u.id, u.email, u.name, u.avatar_color, u.role, bm.role as board_role
|
|
FROM board_members bm
|
|
JOIN users u ON u.id = bm.user_id
|
|
WHERE bm.board_id = ?
|
|
`).all(req.params.boardId);
|
|
res.json(members);
|
|
});
|
|
|
|
export default router;
|