Skip to content

Commit ebf40b7

Browse files
committed
feat: Add 12 enhancements - security, testing, UX, API docs, real-time, search, dark mode
Phase 1 - Security: - Rate limiting middleware (auth, API routes) - Input validation with express-validator - Password reset flow (forgot/reset endpoints) - Token refresh and logout endpoints Phase 2 - Testing: - Jest + Supertest + mongodb-memory-server setup - Auth, tasks, and projects controller tests (25 tests) Phase 3 - UX: - Toast notification system (ToastContext + Toast component) - Settings page with profile editing and password change Phase 4 - API Documentation: - Swagger/OpenAPI setup at /api-docs - JSDoc annotations for auth routes Phase 5 - Real-time: - Socket.io server with JWT auth - SocketContext for frontend real-time updates Phase 6 - Search & Analytics: - Global search endpoint (tasks, projects, users) - SearchBar component with debounced search - Reports page with sprint velocity charts Phase 7 - UI: - Dark mode with ThemeContext and CSS variables - Keyboard shortcuts component (Ctrl+K for search)
1 parent 665bfa9 commit ebf40b7

40 files changed

+12141
-2074
lines changed

DIAGRAMS.md

Lines changed: 650 additions & 0 deletions
Large diffs are not rendered by default.

architecture_diagram.dot

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Architecture Trois Tiers - FlowOps
2+
// Compiler avec: dot -Tpng architecture_diagram.dot -o architecture_diagram.png
3+
// Ou: dot -Tsvg architecture_diagram.dot -o architecture_diagram.svg
4+
5+
digraph ArchitectureTroisTiers {
6+
// Configuration générale
7+
graph [
8+
rankdir=TB
9+
splines=ortho
10+
nodesep=0.8
11+
ranksep=1.0
12+
bgcolor="white"
13+
fontname="Arial"
14+
label=<<B>Architecture Trois Tiers</B>>
15+
labelloc=t
16+
fontsize=20
17+
]
18+
19+
node [
20+
shape=box
21+
style="filled,rounded"
22+
fontname="Arial"
23+
fontsize=12
24+
margin="0.3,0.2"
25+
]
26+
27+
edge [
28+
fontname="Arial"
29+
fontsize=10
30+
color="#333333"
31+
]
32+
33+
// ===========================================
34+
// COUCHE PRÉSENTATION
35+
// ===========================================
36+
subgraph cluster_presentation {
37+
label=<<B>Couche Présentation</B>>
38+
labelloc=t
39+
style="rounded,filled"
40+
fillcolor="#E3F2FD"
41+
color="#1976D2"
42+
fontcolor="#1976D2"
43+
fontsize=14
44+
45+
presentation [
46+
label=<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
47+
<TR><TD><B>Présentation</B></TD></TR>
48+
<TR><TD> </TD></TR>
49+
<TR><TD>React + Vite + Nginx</TD></TR>
50+
</TABLE>>
51+
fillcolor="#BBDEFB"
52+
color="#1976D2"
53+
]
54+
}
55+
56+
// ===========================================
57+
// COUCHE MÉTIER
58+
// ===========================================
59+
subgraph cluster_metier {
60+
label=<<B>Couche Métier</B>>
61+
labelloc=t
62+
style="rounded,filled"
63+
fillcolor="#E8F5E9"
64+
color="#388E3C"
65+
fontcolor="#388E3C"
66+
fontsize=14
67+
68+
metier [
69+
label=<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
70+
<TR><TD><B>Métier</B></TD></TR>
71+
<TR><TD> </TD></TR>
72+
<TR><TD>Node.js + Express.js</TD></TR>
73+
</TABLE>>
74+
fillcolor="#C8E6C9"
75+
color="#388E3C"
76+
]
77+
}
78+
79+
// ===========================================
80+
// COUCHE DONNÉES
81+
// ===========================================
82+
subgraph cluster_donnees {
83+
label=<<B>Couche Données</B>>
84+
labelloc=t
85+
style="rounded,filled"
86+
fillcolor="#FFF3E0"
87+
color="#F57C00"
88+
fontcolor="#F57C00"
89+
fontsize=14
90+
91+
donnees [
92+
label=<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
93+
<TR><TD><B>Données</B></TD></TR>
94+
<TR><TD> </TD></TR>
95+
<TR><TD>Azure Cosmos DB (MongoDB)</TD></TR>
96+
</TABLE>>
97+
fillcolor="#FFE0B2"
98+
color="#F57C00"
99+
shape=cylinder
100+
]
101+
}
102+
103+
// ===========================================
104+
// CONNEXIONS
105+
// ===========================================
106+
presentation -> metier [
107+
label=" HTTPS / REST API "
108+
dir=both
109+
style=bold
110+
color="#1976D2"
111+
fontcolor="#1976D2"
112+
]
113+
114+
metier -> donnees [
115+
label=" MongoDB Protocol "
116+
dir=both
117+
style=bold
118+
color="#388E3C"
119+
fontcolor="#388E3C"
120+
]
121+
}

architecture_simple.dot

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Architecture Trois Tiers - Version Simple
2+
// Pour LaTeX: dot2texi ou directement avec graphviz
3+
// Compiler: dot -Tpng architecture_simple.dot -o architecture_simple.png
4+
5+
digraph Architecture {
6+
// Configuration
7+
graph [
8+
rankdir=TB
9+
splines=line
10+
nodesep=0.5
11+
ranksep=0.8
12+
bgcolor="transparent"
13+
fontname="Helvetica"
14+
label="Architecture Trois Tiers"
15+
labelloc=t
16+
fontsize=16
17+
]
18+
19+
node [
20+
shape=box
21+
style="filled"
22+
fontname="Helvetica"
23+
fontsize=11
24+
width=3
25+
height=0.6
26+
]
27+
28+
edge [
29+
fontname="Helvetica"
30+
fontsize=10
31+
]
32+
33+
// Noeuds
34+
presentation [
35+
label="Présentation : React + Vite + Nginx"
36+
fillcolor="#E3F2FD"
37+
]
38+
39+
protocol1 [
40+
label="↓ HTTPS / REST API"
41+
shape=plaintext
42+
fillcolor="transparent"
43+
]
44+
45+
metier [
46+
label="Métier : Node.js + Express.js"
47+
fillcolor="#E8F5E9"
48+
]
49+
50+
protocol2 [
51+
label="↓ MongoDB Protocol"
52+
shape=plaintext
53+
fillcolor="transparent"
54+
]
55+
56+
donnees [
57+
label="Données : Azure Cosmos DB (MongoDB)"
58+
fillcolor="#FFF3E0"
59+
]
60+
61+
// Connexions (invisibles pour garder l'ordre)
62+
presentation -> protocol1 [style=invis]
63+
protocol1 -> metier [style=invis]
64+
metier -> protocol2 [style=invis]
65+
protocol2 -> donnees [style=invis]
66+
}

backend/config/socket.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const { Server } = require('socket.io');
2+
const jwt = require('jsonwebtoken');
3+
const User = require('../models/User');
4+
5+
let io;
6+
7+
const initializeSocket = (server) => {
8+
io = new Server(server, {
9+
cors: {
10+
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
11+
methods: ['GET', 'POST'],
12+
credentials: true
13+
}
14+
});
15+
16+
// Authentication middleware
17+
io.use(async (socket, next) => {
18+
try {
19+
const token = socket.handshake.auth.token;
20+
if (!token) {
21+
return next(new Error('Authentication required'));
22+
}
23+
24+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
25+
const user = await User.findById(decoded.id);
26+
27+
if (!user) {
28+
return next(new Error('User not found'));
29+
}
30+
31+
socket.user = user;
32+
next();
33+
} catch (err) {
34+
next(new Error('Invalid token'));
35+
}
36+
});
37+
38+
io.on('connection', (socket) => {
39+
console.log(`User connected: ${socket.user.name} (${socket.id})`);
40+
41+
// Join user's personal room
42+
socket.join(`user:${socket.user._id}`);
43+
44+
// Join project rooms
45+
socket.on('join:project', (projectId) => {
46+
socket.join(`project:${projectId}`);
47+
console.log(`${socket.user.name} joined project: ${projectId}`);
48+
});
49+
50+
socket.on('leave:project', (projectId) => {
51+
socket.leave(`project:${projectId}`);
52+
console.log(`${socket.user.name} left project: ${projectId}`);
53+
});
54+
55+
socket.on('disconnect', () => {
56+
console.log(`User disconnected: ${socket.user.name}`);
57+
});
58+
});
59+
60+
return io;
61+
};
62+
63+
const getIO = () => {
64+
if (!io) {
65+
throw new Error('Socket.io not initialized');
66+
}
67+
return io;
68+
};
69+
70+
// Helper functions to emit events
71+
const emitToProject = (projectId, event, data) => {
72+
if (io) {
73+
io.to(`project:${projectId}`).emit(event, data);
74+
}
75+
};
76+
77+
const emitToUser = (userId, event, data) => {
78+
if (io) {
79+
io.to(`user:${userId}`).emit(event, data);
80+
}
81+
};
82+
83+
module.exports = {
84+
initializeSocket,
85+
getIO,
86+
emitToProject,
87+
emitToUser
88+
};

backend/config/swagger.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const swaggerJsDoc = require('swagger-jsdoc');
2+
3+
const options = {
4+
definition: {
5+
openapi: '3.0.0',
6+
info: {
7+
title: 'FlowOps API',
8+
version: '1.0.2',
9+
description: 'Project Management API - Jira-like task and project management system',
10+
contact: {
11+
name: 'FlowOps Team'
12+
}
13+
},
14+
servers: [
15+
{
16+
url: 'http://localhost:3001/api',
17+
description: 'Development server'
18+
}
19+
],
20+
components: {
21+
securitySchemes: {
22+
bearerAuth: {
23+
type: 'http',
24+
scheme: 'bearer',
25+
bearerFormat: 'JWT'
26+
}
27+
},
28+
schemas: {
29+
User: {
30+
type: 'object',
31+
properties: {
32+
id: { type: 'string' },
33+
name: { type: 'string' },
34+
email: { type: 'string', format: 'email' },
35+
role: { type: 'string', enum: ['admin', 'project_manager', 'member'] }
36+
}
37+
},
38+
Project: {
39+
type: 'object',
40+
properties: {
41+
_id: { type: 'string' },
42+
name: { type: 'string' },
43+
key: { type: 'string' },
44+
description: { type: 'string' },
45+
status: { type: 'string', enum: ['planned', 'in_progress', 'completed'] },
46+
owner: { type: 'string' },
47+
members: { type: 'array', items: { type: 'string' } },
48+
createdAt: { type: 'string', format: 'date-time' }
49+
}
50+
},
51+
Task: {
52+
type: 'object',
53+
properties: {
54+
_id: { type: 'string' },
55+
title: { type: 'string' },
56+
description: { type: 'string' },
57+
taskKey: { type: 'string' },
58+
type: { type: 'string', enum: ['task', 'bug', 'story', 'epic', 'subtask'] },
59+
status: { type: 'string', enum: ['todo', 'doing', 'review', 'done'] },
60+
priority: { type: 'string', enum: ['lowest', 'low', 'medium', 'high', 'highest'] },
61+
storyPoints: { type: 'number' },
62+
project: { type: 'string' },
63+
assignee: { type: 'string' },
64+
reporter: { type: 'string' },
65+
createdAt: { type: 'string', format: 'date-time' }
66+
}
67+
},
68+
Error: {
69+
type: 'object',
70+
properties: {
71+
success: { type: 'boolean', example: false },
72+
message: { type: 'string' }
73+
}
74+
}
75+
}
76+
},
77+
security: [{ bearerAuth: [] }]
78+
},
79+
apis: ['./routes/*.js']
80+
};
81+
82+
const swaggerSpec = swaggerJsDoc(options);
83+
84+
module.exports = swaggerSpec;

0 commit comments

Comments
 (0)