@atlashub/smartstack-cli 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.documentation/agents.html +916 -916
  2. package/.documentation/apex.html +1018 -1018
  3. package/.documentation/business-analyse.html +1501 -1501
  4. package/.documentation/commands.html +680 -680
  5. package/.documentation/css/styles.css +2168 -2168
  6. package/.documentation/efcore.html +2505 -2505
  7. package/.documentation/gitflow.html +2618 -2618
  8. package/.documentation/hooks.html +413 -413
  9. package/.documentation/index.html +323 -323
  10. package/.documentation/installation.html +462 -462
  11. package/.documentation/js/app.js +794 -794
  12. package/.documentation/test-web.html +513 -513
  13. package/dist/index.js +807 -277
  14. package/dist/index.js.map +1 -1
  15. package/package.json +1 -1
  16. package/templates/agents/efcore/conflicts.md +44 -17
  17. package/templates/agents/efcore/db-status.md +27 -6
  18. package/templates/agents/efcore/scan.md +43 -13
  19. package/templates/commands/ai-prompt.md +315 -315
  20. package/templates/commands/application/create.md +362 -362
  21. package/templates/commands/controller/create.md +216 -216
  22. package/templates/commands/controller.md +59 -0
  23. package/templates/commands/documentation/module.md +202 -202
  24. package/templates/commands/efcore/_env-check.md +153 -153
  25. package/templates/commands/efcore/conflicts.md +109 -192
  26. package/templates/commands/efcore/db-status.md +101 -89
  27. package/templates/commands/efcore/migration.md +23 -11
  28. package/templates/commands/efcore/scan.md +115 -119
  29. package/templates/commands/efcore.md +54 -6
  30. package/templates/commands/feature-full.md +267 -267
  31. package/templates/commands/gitflow/11-finish.md +145 -11
  32. package/templates/commands/gitflow/13-sync.md +216 -216
  33. package/templates/commands/gitflow/14-rebase.md +251 -251
  34. package/templates/commands/gitflow/2-status.md +120 -10
  35. package/templates/commands/gitflow/3-commit.md +150 -0
  36. package/templates/commands/gitflow/7-pull-request.md +134 -5
  37. package/templates/commands/gitflow/9-merge.md +142 -1
  38. package/templates/commands/implement.md +663 -663
  39. package/templates/commands/init.md +562 -0
  40. package/templates/commands/mcp-integration.md +330 -0
  41. package/templates/commands/notification.md +129 -129
  42. package/templates/commands/validate.md +233 -0
  43. package/templates/commands/workflow.md +193 -193
  44. package/templates/skills/ai-prompt/SKILL.md +778 -778
  45. package/templates/skills/application/SKILL.md +563 -563
  46. package/templates/skills/application/templates-backend.md +450 -450
  47. package/templates/skills/application/templates-frontend.md +531 -531
  48. package/templates/skills/application/templates-i18n.md +520 -520
  49. package/templates/skills/application/templates-seed.md +647 -647
  50. package/templates/skills/controller/SKILL.md +240 -240
  51. package/templates/skills/controller/postman-templates.md +614 -614
  52. package/templates/skills/controller/templates.md +1468 -1468
  53. package/templates/skills/documentation/SKILL.md +133 -133
  54. package/templates/skills/documentation/templates.md +476 -476
  55. package/templates/skills/feature-full/SKILL.md +838 -838
  56. package/templates/skills/notification/SKILL.md +555 -555
  57. package/templates/skills/ui-components/SKILL.md +870 -870
  58. package/templates/skills/workflow/SKILL.md +582 -582
@@ -1,555 +1,555 @@
1
- ---
2
- name: notification
3
- description: |
4
- Integre le systeme de notifications SmartStack dans le developpement.
5
- Utiliser ce skill quand:
6
- - L'utilisateur veut envoyer des notifications depuis une feature
7
- - L'utilisateur mentionne "notifier", "alerte", "notification", "in-app"
8
- - Creation d'un module qui necessite des notifications utilisateur
9
- - Integration SignalR pour real-time
10
- Types: In-App, Email (via Workflow), Push (futur)
11
- ---
12
-
13
- # Skill Notification SmartStack
14
-
15
- > **Architecture:** Notifications = In-App (DB + SignalR) + Email (via Workflows)
16
- > Les notifications push sont prevues mais pas encore implementees.
17
-
18
- ## QUAND CE SKILL S'ACTIVE
19
-
20
- Claude invoque automatiquement ce skill quand il detecte :
21
-
22
- | Declencheur | Exemple |
23
- |-------------|---------|
24
- | Demande explicite | "Notifie l'utilisateur quand un ticket est cree" |
25
- | Mention notification | "Il faut alerter l'admin si..." |
26
- | Event-driven | "Quand le SLA expire, prevenir l'utilisateur" |
27
- | Real-time | "Afficher en temps reel les nouvelles notifications" |
28
- | Mots-cles | "notifier", "alerte", "notification", "SignalR", "real-time" |
29
-
30
- ---
31
-
32
- ## ARCHITECTURE NOTIFICATIONS
33
-
34
- ```
35
- ┌─────────────────────────────────────────────────────────────────────────────┐
36
- │ NOTIFICATION FLOW │
37
- ├─────────────────────────────────────────────────────────────────────────────┤
38
- │ │
39
- │ [EVENT] ─────┬──────────────────────────────────────────────────────────┐ │
40
- │ │ │ │
41
- │ ▼ │ │
42
- │ ┌─────────────────────────┐ │ │
43
- │ │ INotificationService │ │ │
44
- │ │ SendNotificationAsync │ │ │
45
- │ └────────────┬────────────┘ │ │
46
- │ │ │ │
47
- │ ┌───────┴───────┐ │ │
48
- │ ▼ ▼ │ │
49
- │ ┌─────────┐ ┌───────────────────┐ │ │
50
- │ │ DB │ │ SignalR Hub │ │ │
51
- │ │ (store) │ │ (real-time push) │ │ │
52
- │ └─────────┘ └─────────┬─────────┘ │ │
53
- │ │ │ │
54
- │ ▼ │ │
55
- │ ┌──────────────────┐ │ │
56
- │ │ Frontend Hook │ │ │
57
- │ │ useSignalR() │ │ │
58
- │ └─────────┬────────┘ │ │
59
- │ │ │ │
60
- │ ▼ │ │
61
- │ ┌──────────────────┐ │ │
62
- │ │ NotificationBell │ │ │
63
- │ │ (UI Component) │ │ │
64
- │ └──────────────────┘ │ │
65
- │ │ │
66
- │ [EMAIL] ── IWorkflowService.TriggerAsync() ── EmailTemplate ── SMTP │ │
67
- │ │ │
68
- └─────────────────────────────────────────────────────────────────────────────┘
69
- ```
70
-
71
- ---
72
-
73
- ## TYPES DE NOTIFICATION
74
-
75
- ### Enum NotificationType
76
-
77
- | Type | Declencheur | Usage |
78
- |------|-------------|-------|
79
- | `TicketCreated` | Nouveau ticket | Support |
80
- | `TicketAssigned` | Ticket assigne | Support |
81
- | `TicketStatusChanged` | Changement statut | Support |
82
- | `TicketCommentAdded` | Nouveau commentaire | Support |
83
- | `TicketResolved` | Ticket resolu | Support |
84
- | `RoleAssigned` | Role attribue | Admin |
85
- | `RoleRemoved` | Role retire | Admin |
86
- | `RolePermissionsChanged` | Permissions modifiees | Admin |
87
- | `SystemAnnouncement` | Annonce systeme | Global |
88
- | `AccountUpdated` | Compte modifie | User |
89
- | `SlaWarning` | SLA proche expiration | SLA |
90
- | `SlaResponseBreached` | SLA reponse depasse | SLA |
91
- | `SlaResolutionBreached` | SLA resolution depasse | SLA |
92
-
93
- ### Ajouter un Nouveau Type
94
-
95
- ```csharp
96
- // 1. Domain/Support/Enums/NotificationType.cs
97
- public enum NotificationType
98
- {
99
- // ... existants ...
100
-
101
- // Ajouter le nouveau type
102
- $NEW_TYPE = XX, // Increment depuis le dernier
103
- }
104
-
105
- // 2. Utilisation dans le service
106
- await _notificationService.SendNotificationAsync(
107
- userId,
108
- NotificationType.$NEW_TYPE,
109
- title,
110
- message,
111
- relatedEntityType: "EntityName",
112
- relatedEntityId: entityId,
113
- actionUrl: "/path/to/entity"
114
- );
115
- ```
116
-
117
- ---
118
-
119
- ## WORKFLOW INTEGRATION
120
-
121
- ### ETAPE 1: Identifier le Besoin
122
-
123
- | Question | Reponse → Action |
124
- |----------|------------------|
125
- | L'utilisateur doit etre notifie en temps reel ? | → In-App + SignalR |
126
- | L'utilisateur doit recevoir un email ? | → Workflow + EmailTemplate |
127
- | L'action est liee a une entite ? | → Ajouter relatedEntityType/Id |
128
- | L'utilisateur peut naviguer vers l'entite ? | → Ajouter actionUrl |
129
-
130
- ### ETAPE 2: Injection du Service
131
-
132
- ```csharp
133
- // Dans le Controller ou Service
134
- public class MyController : ControllerBase
135
- {
136
- private readonly INotificationService _notificationService;
137
- private readonly ILogger<MyController> _logger;
138
-
139
- public MyController(
140
- INotificationService notificationService,
141
- ILogger<MyController> logger)
142
- {
143
- _notificationService = notificationService;
144
- _logger = logger;
145
- }
146
- }
147
- ```
148
-
149
- ### ETAPE 3: Envoi de Notification
150
-
151
- ```csharp
152
- // Notification simple
153
- await _notificationService.SendNotificationAsync(
154
- userId: targetUserId,
155
- type: NotificationType.TicketCreated,
156
- title: "Nouveau ticket",
157
- message: $"Le ticket #{ticket.Number} a ete cree",
158
- cancellationToken: ct
159
- );
160
-
161
- // Notification avec entite liee
162
- await _notificationService.SendNotificationAsync(
163
- userId: ticket.AssignedToId.Value,
164
- type: NotificationType.TicketAssigned,
165
- title: "Ticket assigne",
166
- message: $"Le ticket #{ticket.Number} vous a ete assigne",
167
- relatedEntityType: "Ticket",
168
- relatedEntityId: ticket.Id,
169
- actionUrl: $"/support/tickets/{ticket.Id}",
170
- cancellationToken: ct
171
- );
172
-
173
- // Notification a plusieurs utilisateurs
174
- await _notificationService.SendNotificationsAsync(
175
- userIds: teamUserIds,
176
- type: NotificationType.SystemAnnouncement,
177
- title: "Annonce equipe",
178
- message: "Reunion planifiee demain a 10h",
179
- cancellationToken: ct
180
- );
181
-
182
- // Notification a un role
183
- await _notificationService.SendNotificationToRoleAsync(
184
- roleName: "Admin",
185
- type: NotificationType.SystemAnnouncement,
186
- title: "Alerte systeme",
187
- message: "Maintenance planifiee ce soir",
188
- cancellationToken: ct
189
- );
190
- ```
191
-
192
- ### ETAPE 4: Preferences Utilisateur
193
-
194
- ```csharp
195
- // Verifier si l'utilisateur veut etre notifie
196
- var (shouldEmail, shouldInApp, shouldPush) =
197
- await _notificationService.ShouldNotifyAsync(userId, notificationType);
198
-
199
- if (shouldInApp)
200
- {
201
- await _notificationService.SendNotificationAsync(...);
202
- }
203
-
204
- if (shouldEmail)
205
- {
206
- // Declencher workflow email
207
- await _workflowService.TriggerAsync("ticket.created", new Dictionary<string, object>
208
- {
209
- ["ticketNumber"] = ticket.Number,
210
- ["ticketTitle"] = ticket.Title,
211
- ["userEmail"] = user.Email,
212
- ["userName"] = user.DisplayName
213
- });
214
- }
215
- ```
216
-
217
- ---
218
-
219
- ## FRONTEND INTEGRATION
220
-
221
- ### Hook useSignalR
222
-
223
- ```typescript
224
- // hooks/useSignalR.ts
225
- import { useSignalR } from '@/hooks/useSignalR';
226
-
227
- function MyComponent() {
228
- useSignalR({
229
- onNotification: (notification) => {
230
- // Notification recue en real-time
231
- console.log('New notification:', notification);
232
- toast.info(notification.title);
233
- },
234
- onUnreadCountUpdate: (count) => {
235
- // Mise a jour du badge
236
- setUnreadCount(count);
237
- },
238
- onPermissionsChanged: () => {
239
- // Permissions modifiees - recharger
240
- queryClient.invalidateQueries(['permissions']);
241
- },
242
- });
243
- }
244
- ```
245
-
246
- ### API Notifications
247
-
248
- ```typescript
249
- // services/api/notificationsApi.ts
250
- import { apiClient } from './apiClient';
251
-
252
- export const notificationsApi = {
253
- // Recuperer les non-lues
254
- getUnread: (limit = 10) =>
255
- apiClient.get<NotificationDto[]>(`/notifications/unread?limit=${limit}`),
256
-
257
- // Recuperer avec pagination
258
- getAll: (page = 1, pageSize = 20, isRead?: boolean) =>
259
- apiClient.get<PagedResult<NotificationDto>>('/notifications', {
260
- params: { page, pageSize, isRead }
261
- }),
262
-
263
- // Compter les non-lues
264
- getUnreadCount: () =>
265
- apiClient.get<{ count: number }>('/notifications/unread/count'),
266
-
267
- // Marquer comme lue
268
- markAsRead: (id: string) =>
269
- apiClient.post(`/notifications/${id}/read`),
270
-
271
- // Marquer toutes comme lues
272
- markAllAsRead: () =>
273
- apiClient.post('/notifications/read-all'),
274
-
275
- // Supprimer
276
- delete: (id: string) =>
277
- apiClient.delete(`/notifications/${id}`),
278
-
279
- // Supprimer toutes
280
- deleteAll: () =>
281
- apiClient.delete('/notifications'),
282
- };
283
- ```
284
-
285
- ### Composant NotificationBell
286
-
287
- ```tsx
288
- // components/notifications/NotificationBell.tsx
289
- import { Bell } from 'lucide-react';
290
- import { useSignalR } from '@/hooks/useSignalR';
291
- import { notificationsApi } from '@/services/api/notificationsApi';
292
-
293
- export function NotificationBell() {
294
- const [notifications, setNotifications] = useState<NotificationDto[]>([]);
295
- const [unreadCount, setUnreadCount] = useState(0);
296
- const [isOpen, setIsOpen] = useState(false);
297
-
298
- // Real-time updates
299
- useSignalR({
300
- onNotification: (notification) => {
301
- setNotifications(prev => [notification, ...prev]);
302
- setUnreadCount(prev => prev + 1);
303
- },
304
- onUnreadCountUpdate: (count) => {
305
- setUnreadCount(count);
306
- },
307
- });
308
-
309
- // Load initial data
310
- useEffect(() => {
311
- loadNotifications();
312
- }, []);
313
-
314
- return (
315
- <div className="relative">
316
- <button onClick={() => setIsOpen(!isOpen)} className="relative p-2">
317
- <Bell className="w-5 h-5" />
318
- {unreadCount > 0 && (
319
- <span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
320
- {unreadCount > 99 ? '99+' : unreadCount}
321
- </span>
322
- )}
323
- </button>
324
-
325
- {isOpen && (
326
- <NotificationDropdown
327
- notifications={notifications}
328
- onMarkAsRead={handleMarkAsRead}
329
- onMarkAllAsRead={handleMarkAllAsRead}
330
- onDelete={handleDelete}
331
- />
332
- )}
333
- </div>
334
- );
335
- }
336
- ```
337
-
338
- ---
339
-
340
- ## TEMPLATES
341
-
342
- ### Template Service (Backend)
343
-
344
- ```csharp
345
- // Services/{Module}/{Module}Service.cs
346
-
347
- public async Task<Result> Create{Entity}Async(Create{Entity}Command command, CancellationToken ct)
348
- {
349
- // ... creation de l'entite ...
350
-
351
- // Notification au createur (confirmation)
352
- await _notificationService.SendNotificationAsync(
353
- _currentUser.Id,
354
- NotificationType.{Entity}Created,
355
- "{Entity} creee",
356
- $"Votre {entity.Name} a ete creee avec succes",
357
- relatedEntityType: "{Entity}",
358
- relatedEntityId: entity.Id,
359
- actionUrl: $"/{module}/{entity.Id}",
360
- ct);
361
-
362
- // Notification aux responsables
363
- if (entity.AssignedToId.HasValue)
364
- {
365
- await _notificationService.SendNotificationAsync(
366
- entity.AssignedToId.Value,
367
- NotificationType.{Entity}Assigned,
368
- "{Entity} assignee",
369
- $"La {entity.Name} vous a ete assignee par {_currentUser.DisplayName}",
370
- relatedEntityType: "{Entity}",
371
- relatedEntityId: entity.Id,
372
- actionUrl: $"/{module}/{entity.Id}",
373
- ct);
374
- }
375
-
376
- return Result.Success();
377
- }
378
- ```
379
-
380
- ### Template Controller (Backend)
381
-
382
- ```csharp
383
- // Controllers/{Area}/{Module}Controller.cs
384
-
385
- [HttpPost]
386
- [RequirePermission(Permissions.{Module}.Create)]
387
- [ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
388
- public async Task<ActionResult<{Entity}Dto>> Create(
389
- [FromBody] Create{Entity}Request request,
390
- CancellationToken ct)
391
- {
392
- var entity = await _service.CreateAsync(request, ct);
393
-
394
- // Notification automatique dans le service
395
- // OU notification ici si logique specifique au controller
396
-
397
- _logger.LogInformation(
398
- "User {UserId} created {EntityType} {EntityId}",
399
- _currentUser.Id, nameof({Entity}), entity.Id);
400
-
401
- return CreatedAtAction(
402
- nameof(GetById),
403
- new { id = entity.Id },
404
- entity);
405
- }
406
- ```
407
-
408
- ### Template Hook (Frontend)
409
-
410
- ```typescript
411
- // hooks/use{Module}Notifications.ts
412
-
413
- import { useSignalR } from '@/hooks/useSignalR';
414
- import { useQueryClient } from '@tanstack/react-query';
415
- import { toast } from 'sonner';
416
-
417
- export function use{Module}Notifications() {
418
- const queryClient = useQueryClient();
419
-
420
- useSignalR({
421
- onNotification: (notification) => {
422
- // Filtrer les notifications de ce module
423
- if (notification.relatedEntityType === '{Entity}') {
424
- // Invalider les queries pour refresh
425
- queryClient.invalidateQueries(['{module}']);
426
-
427
- // Toast avec action
428
- toast.info(notification.title, {
429
- description: notification.message,
430
- action: notification.actionUrl ? {
431
- label: 'Voir',
432
- onClick: () => navigate(notification.actionUrl),
433
- } : undefined,
434
- });
435
- }
436
- },
437
- });
438
- }
439
- ```
440
-
441
- ---
442
-
443
- ## CHECKLIST INTEGRATION
444
-
445
- ```
446
- □ Type de notification identifie (existant ou nouveau)
447
- □ Si nouveau type: ajoute a NotificationType enum
448
- □ Service injecte: INotificationService
449
- □ Notification envoyee avec tous les champs:
450
- □ userId (destinataire)
451
- □ type (NotificationType)
452
- □ title (titre court)
453
- □ message (description)
454
- □ relatedEntityType (optionnel)
455
- □ relatedEntityId (optionnel)
456
- □ actionUrl (optionnel)
457
- □ Logging ajoute (LogInformation)
458
- □ Frontend: useSignalR hook configure
459
- □ Frontend: toast ou UI update sur reception
460
- □ Si email requis: workflow configure
461
- ```
462
-
463
- ---
464
-
465
- ## PATTERNS AVANCES
466
-
467
- ### Pattern: Notification avec Delai (SLA)
468
-
469
- ```csharp
470
- // Pour les SLA warnings, utiliser un job Hangfire
471
- public class SlaNotificationJob
472
- {
473
- public async Task CheckSlaBreaches()
474
- {
475
- var tickets = await _context.Tickets
476
- .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30))
477
- .Where(t => !t.SlaWarningNotificationSent)
478
- .ToListAsync();
479
-
480
- foreach (var ticket in tickets)
481
- {
482
- await _notificationService.SendNotificationAsync(
483
- ticket.AssignedToId ?? ticket.CreatedById,
484
- NotificationType.SlaWarning,
485
- "SLA proche expiration",
486
- $"Le ticket #{ticket.Number} expire dans 30 minutes",
487
- relatedEntityType: "Ticket",
488
- relatedEntityId: ticket.Id,
489
- actionUrl: $"/support/tickets/{ticket.Id}");
490
-
491
- ticket.SlaWarningNotificationSent = true;
492
- }
493
-
494
- await _context.SaveChangesAsync();
495
- }
496
- }
497
- ```
498
-
499
- ### Pattern: Notification Groupee
500
-
501
- ```csharp
502
- // Pour eviter le spam, grouper les notifications similaires
503
- public async Task SendBatchNotification(
504
- Guid userId,
505
- NotificationType type,
506
- List<EntitySummary> entities)
507
- {
508
- if (entities.Count == 1)
509
- {
510
- await _notificationService.SendNotificationAsync(
511
- userId, type,
512
- $"Nouveau {entities[0].Type}",
513
- entities[0].Description,
514
- relatedEntityType: entities[0].Type,
515
- relatedEntityId: entities[0].Id);
516
- }
517
- else
518
- {
519
- await _notificationService.SendNotificationAsync(
520
- userId, type,
521
- $"{entities.Count} nouveaux elements",
522
- $"Vous avez {entities.Count} nouveaux {entities[0].Type}s a traiter",
523
- actionUrl: "/dashboard/pending");
524
- }
525
- }
526
- ```
527
-
528
- ---
529
-
530
- ## REGLES ABSOLUES
531
-
532
- 1. **TOUJOURS** utiliser INotificationService (jamais acces DB direct)
533
- 2. **TOUJOURS** utiliser les types NotificationType enum
534
- 3. **TOUJOURS** inclure le context (relatedEntityType/Id) quand applicable
535
- 4. **TOUJOURS** ajouter actionUrl pour navigation
536
- 5. **TOUJOURS** logger les notifications envoyees
537
- 6. **TOUJOURS** respecter les preferences utilisateur
538
- 7. **JAMAIS** envoyer de notification sans destinataire valide
539
- 8. **JAMAIS** hardcoder les messages (utiliser i18n ou templates)
540
- 9. **JAMAIS** spam l'utilisateur (grouper les notifications similaires)
541
- 10. **JAMAIS** oublier le CancellationToken
542
-
543
- ---
544
-
545
- ## FICHIERS CLES
546
-
547
- | Fichier | Role |
548
- |---------|------|
549
- | `Domain/Support/Notification.cs` | Entite notification |
550
- | `Domain/Support/Enums/NotificationType.cs` | Types de notification |
551
- | `Application/Common/Interfaces/INotificationService.cs` | Interface service |
552
- | `Infrastructure/Services/Support/NotificationService.cs` | Implementation |
553
- | `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
554
- | `web/src/hooks/useSignalR.ts` | Hook frontend |
555
- | `web/src/components/notifications/NotificationBell.tsx` | UI Component |
1
+ ---
2
+ name: notification
3
+ description: |
4
+ Integre le systeme de notifications SmartStack dans le developpement.
5
+ Utiliser ce skill quand:
6
+ - L'utilisateur veut envoyer des notifications depuis une feature
7
+ - L'utilisateur mentionne "notifier", "alerte", "notification", "in-app"
8
+ - Creation d'un module qui necessite des notifications utilisateur
9
+ - Integration SignalR pour real-time
10
+ Types: In-App, Email (via Workflow), Push (futur)
11
+ ---
12
+
13
+ # Skill Notification SmartStack
14
+
15
+ > **Architecture:** Notifications = In-App (DB + SignalR) + Email (via Workflows)
16
+ > Les notifications push sont prevues mais pas encore implementees.
17
+
18
+ ## QUAND CE SKILL S'ACTIVE
19
+
20
+ Claude invoque automatiquement ce skill quand il detecte :
21
+
22
+ | Declencheur | Exemple |
23
+ |-------------|---------|
24
+ | Demande explicite | "Notifie l'utilisateur quand un ticket est cree" |
25
+ | Mention notification | "Il faut alerter l'admin si..." |
26
+ | Event-driven | "Quand le SLA expire, prevenir l'utilisateur" |
27
+ | Real-time | "Afficher en temps reel les nouvelles notifications" |
28
+ | Mots-cles | "notifier", "alerte", "notification", "SignalR", "real-time" |
29
+
30
+ ---
31
+
32
+ ## ARCHITECTURE NOTIFICATIONS
33
+
34
+ ```
35
+ ┌─────────────────────────────────────────────────────────────────────────────┐
36
+ │ NOTIFICATION FLOW │
37
+ ├─────────────────────────────────────────────────────────────────────────────┤
38
+ │ │
39
+ │ [EVENT] ─────┬──────────────────────────────────────────────────────────┐ │
40
+ │ │ │ │
41
+ │ ▼ │ │
42
+ │ ┌─────────────────────────┐ │ │
43
+ │ │ INotificationService │ │ │
44
+ │ │ SendNotificationAsync │ │ │
45
+ │ └────────────┬────────────┘ │ │
46
+ │ │ │ │
47
+ │ ┌───────┴───────┐ │ │
48
+ │ ▼ ▼ │ │
49
+ │ ┌─────────┐ ┌───────────────────┐ │ │
50
+ │ │ DB │ │ SignalR Hub │ │ │
51
+ │ │ (store) │ │ (real-time push) │ │ │
52
+ │ └─────────┘ └─────────┬─────────┘ │ │
53
+ │ │ │ │
54
+ │ ▼ │ │
55
+ │ ┌──────────────────┐ │ │
56
+ │ │ Frontend Hook │ │ │
57
+ │ │ useSignalR() │ │ │
58
+ │ └─────────┬────────┘ │ │
59
+ │ │ │ │
60
+ │ ▼ │ │
61
+ │ ┌──────────────────┐ │ │
62
+ │ │ NotificationBell │ │ │
63
+ │ │ (UI Component) │ │ │
64
+ │ └──────────────────┘ │ │
65
+ │ │ │
66
+ │ [EMAIL] ── IWorkflowService.TriggerAsync() ── EmailTemplate ── SMTP │ │
67
+ │ │ │
68
+ └─────────────────────────────────────────────────────────────────────────────┘
69
+ ```
70
+
71
+ ---
72
+
73
+ ## TYPES DE NOTIFICATION
74
+
75
+ ### Enum NotificationType
76
+
77
+ | Type | Declencheur | Usage |
78
+ |------|-------------|-------|
79
+ | `TicketCreated` | Nouveau ticket | Support |
80
+ | `TicketAssigned` | Ticket assigne | Support |
81
+ | `TicketStatusChanged` | Changement statut | Support |
82
+ | `TicketCommentAdded` | Nouveau commentaire | Support |
83
+ | `TicketResolved` | Ticket resolu | Support |
84
+ | `RoleAssigned` | Role attribue | Admin |
85
+ | `RoleRemoved` | Role retire | Admin |
86
+ | `RolePermissionsChanged` | Permissions modifiees | Admin |
87
+ | `SystemAnnouncement` | Annonce systeme | Global |
88
+ | `AccountUpdated` | Compte modifie | User |
89
+ | `SlaWarning` | SLA proche expiration | SLA |
90
+ | `SlaResponseBreached` | SLA reponse depasse | SLA |
91
+ | `SlaResolutionBreached` | SLA resolution depasse | SLA |
92
+
93
+ ### Ajouter un Nouveau Type
94
+
95
+ ```csharp
96
+ // 1. Domain/Support/Enums/NotificationType.cs
97
+ public enum NotificationType
98
+ {
99
+ // ... existants ...
100
+
101
+ // Ajouter le nouveau type
102
+ $NEW_TYPE = XX, // Increment depuis le dernier
103
+ }
104
+
105
+ // 2. Utilisation dans le service
106
+ await _notificationService.SendNotificationAsync(
107
+ userId,
108
+ NotificationType.$NEW_TYPE,
109
+ title,
110
+ message,
111
+ relatedEntityType: "EntityName",
112
+ relatedEntityId: entityId,
113
+ actionUrl: "/path/to/entity"
114
+ );
115
+ ```
116
+
117
+ ---
118
+
119
+ ## WORKFLOW INTEGRATION
120
+
121
+ ### ETAPE 1: Identifier le Besoin
122
+
123
+ | Question | Reponse → Action |
124
+ |----------|------------------|
125
+ | L'utilisateur doit etre notifie en temps reel ? | → In-App + SignalR |
126
+ | L'utilisateur doit recevoir un email ? | → Workflow + EmailTemplate |
127
+ | L'action est liee a une entite ? | → Ajouter relatedEntityType/Id |
128
+ | L'utilisateur peut naviguer vers l'entite ? | → Ajouter actionUrl |
129
+
130
+ ### ETAPE 2: Injection du Service
131
+
132
+ ```csharp
133
+ // Dans le Controller ou Service
134
+ public class MyController : ControllerBase
135
+ {
136
+ private readonly INotificationService _notificationService;
137
+ private readonly ILogger<MyController> _logger;
138
+
139
+ public MyController(
140
+ INotificationService notificationService,
141
+ ILogger<MyController> logger)
142
+ {
143
+ _notificationService = notificationService;
144
+ _logger = logger;
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### ETAPE 3: Envoi de Notification
150
+
151
+ ```csharp
152
+ // Notification simple
153
+ await _notificationService.SendNotificationAsync(
154
+ userId: targetUserId,
155
+ type: NotificationType.TicketCreated,
156
+ title: "Nouveau ticket",
157
+ message: $"Le ticket #{ticket.Number} a ete cree",
158
+ cancellationToken: ct
159
+ );
160
+
161
+ // Notification avec entite liee
162
+ await _notificationService.SendNotificationAsync(
163
+ userId: ticket.AssignedToId.Value,
164
+ type: NotificationType.TicketAssigned,
165
+ title: "Ticket assigne",
166
+ message: $"Le ticket #{ticket.Number} vous a ete assigne",
167
+ relatedEntityType: "Ticket",
168
+ relatedEntityId: ticket.Id,
169
+ actionUrl: $"/support/tickets/{ticket.Id}",
170
+ cancellationToken: ct
171
+ );
172
+
173
+ // Notification a plusieurs utilisateurs
174
+ await _notificationService.SendNotificationsAsync(
175
+ userIds: teamUserIds,
176
+ type: NotificationType.SystemAnnouncement,
177
+ title: "Annonce equipe",
178
+ message: "Reunion planifiee demain a 10h",
179
+ cancellationToken: ct
180
+ );
181
+
182
+ // Notification a un role
183
+ await _notificationService.SendNotificationToRoleAsync(
184
+ roleName: "Admin",
185
+ type: NotificationType.SystemAnnouncement,
186
+ title: "Alerte systeme",
187
+ message: "Maintenance planifiee ce soir",
188
+ cancellationToken: ct
189
+ );
190
+ ```
191
+
192
+ ### ETAPE 4: Preferences Utilisateur
193
+
194
+ ```csharp
195
+ // Verifier si l'utilisateur veut etre notifie
196
+ var (shouldEmail, shouldInApp, shouldPush) =
197
+ await _notificationService.ShouldNotifyAsync(userId, notificationType);
198
+
199
+ if (shouldInApp)
200
+ {
201
+ await _notificationService.SendNotificationAsync(...);
202
+ }
203
+
204
+ if (shouldEmail)
205
+ {
206
+ // Declencher workflow email
207
+ await _workflowService.TriggerAsync("ticket.created", new Dictionary<string, object>
208
+ {
209
+ ["ticketNumber"] = ticket.Number,
210
+ ["ticketTitle"] = ticket.Title,
211
+ ["userEmail"] = user.Email,
212
+ ["userName"] = user.DisplayName
213
+ });
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ## FRONTEND INTEGRATION
220
+
221
+ ### Hook useSignalR
222
+
223
+ ```typescript
224
+ // hooks/useSignalR.ts
225
+ import { useSignalR } from '@/hooks/useSignalR';
226
+
227
+ function MyComponent() {
228
+ useSignalR({
229
+ onNotification: (notification) => {
230
+ // Notification recue en real-time
231
+ console.log('New notification:', notification);
232
+ toast.info(notification.title);
233
+ },
234
+ onUnreadCountUpdate: (count) => {
235
+ // Mise a jour du badge
236
+ setUnreadCount(count);
237
+ },
238
+ onPermissionsChanged: () => {
239
+ // Permissions modifiees - recharger
240
+ queryClient.invalidateQueries(['permissions']);
241
+ },
242
+ });
243
+ }
244
+ ```
245
+
246
+ ### API Notifications
247
+
248
+ ```typescript
249
+ // services/api/notificationsApi.ts
250
+ import { apiClient } from './apiClient';
251
+
252
+ export const notificationsApi = {
253
+ // Recuperer les non-lues
254
+ getUnread: (limit = 10) =>
255
+ apiClient.get<NotificationDto[]>(`/notifications/unread?limit=${limit}`),
256
+
257
+ // Recuperer avec pagination
258
+ getAll: (page = 1, pageSize = 20, isRead?: boolean) =>
259
+ apiClient.get<PagedResult<NotificationDto>>('/notifications', {
260
+ params: { page, pageSize, isRead }
261
+ }),
262
+
263
+ // Compter les non-lues
264
+ getUnreadCount: () =>
265
+ apiClient.get<{ count: number }>('/notifications/unread/count'),
266
+
267
+ // Marquer comme lue
268
+ markAsRead: (id: string) =>
269
+ apiClient.post(`/notifications/${id}/read`),
270
+
271
+ // Marquer toutes comme lues
272
+ markAllAsRead: () =>
273
+ apiClient.post('/notifications/read-all'),
274
+
275
+ // Supprimer
276
+ delete: (id: string) =>
277
+ apiClient.delete(`/notifications/${id}`),
278
+
279
+ // Supprimer toutes
280
+ deleteAll: () =>
281
+ apiClient.delete('/notifications'),
282
+ };
283
+ ```
284
+
285
+ ### Composant NotificationBell
286
+
287
+ ```tsx
288
+ // components/notifications/NotificationBell.tsx
289
+ import { Bell } from 'lucide-react';
290
+ import { useSignalR } from '@/hooks/useSignalR';
291
+ import { notificationsApi } from '@/services/api/notificationsApi';
292
+
293
+ export function NotificationBell() {
294
+ const [notifications, setNotifications] = useState<NotificationDto[]>([]);
295
+ const [unreadCount, setUnreadCount] = useState(0);
296
+ const [isOpen, setIsOpen] = useState(false);
297
+
298
+ // Real-time updates
299
+ useSignalR({
300
+ onNotification: (notification) => {
301
+ setNotifications(prev => [notification, ...prev]);
302
+ setUnreadCount(prev => prev + 1);
303
+ },
304
+ onUnreadCountUpdate: (count) => {
305
+ setUnreadCount(count);
306
+ },
307
+ });
308
+
309
+ // Load initial data
310
+ useEffect(() => {
311
+ loadNotifications();
312
+ }, []);
313
+
314
+ return (
315
+ <div className="relative">
316
+ <button onClick={() => setIsOpen(!isOpen)} className="relative p-2">
317
+ <Bell className="w-5 h-5" />
318
+ {unreadCount > 0 && (
319
+ <span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
320
+ {unreadCount > 99 ? '99+' : unreadCount}
321
+ </span>
322
+ )}
323
+ </button>
324
+
325
+ {isOpen && (
326
+ <NotificationDropdown
327
+ notifications={notifications}
328
+ onMarkAsRead={handleMarkAsRead}
329
+ onMarkAllAsRead={handleMarkAllAsRead}
330
+ onDelete={handleDelete}
331
+ />
332
+ )}
333
+ </div>
334
+ );
335
+ }
336
+ ```
337
+
338
+ ---
339
+
340
+ ## TEMPLATES
341
+
342
+ ### Template Service (Backend)
343
+
344
+ ```csharp
345
+ // Services/{Module}/{Module}Service.cs
346
+
347
+ public async Task<Result> Create{Entity}Async(Create{Entity}Command command, CancellationToken ct)
348
+ {
349
+ // ... creation de l'entite ...
350
+
351
+ // Notification au createur (confirmation)
352
+ await _notificationService.SendNotificationAsync(
353
+ _currentUser.Id,
354
+ NotificationType.{Entity}Created,
355
+ "{Entity} creee",
356
+ $"Votre {entity.Name} a ete creee avec succes",
357
+ relatedEntityType: "{Entity}",
358
+ relatedEntityId: entity.Id,
359
+ actionUrl: $"/{module}/{entity.Id}",
360
+ ct);
361
+
362
+ // Notification aux responsables
363
+ if (entity.AssignedToId.HasValue)
364
+ {
365
+ await _notificationService.SendNotificationAsync(
366
+ entity.AssignedToId.Value,
367
+ NotificationType.{Entity}Assigned,
368
+ "{Entity} assignee",
369
+ $"La {entity.Name} vous a ete assignee par {_currentUser.DisplayName}",
370
+ relatedEntityType: "{Entity}",
371
+ relatedEntityId: entity.Id,
372
+ actionUrl: $"/{module}/{entity.Id}",
373
+ ct);
374
+ }
375
+
376
+ return Result.Success();
377
+ }
378
+ ```
379
+
380
+ ### Template Controller (Backend)
381
+
382
+ ```csharp
383
+ // Controllers/{Area}/{Module}Controller.cs
384
+
385
+ [HttpPost]
386
+ [RequirePermission(Permissions.{Module}.Create)]
387
+ [ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
388
+ public async Task<ActionResult<{Entity}Dto>> Create(
389
+ [FromBody] Create{Entity}Request request,
390
+ CancellationToken ct)
391
+ {
392
+ var entity = await _service.CreateAsync(request, ct);
393
+
394
+ // Notification automatique dans le service
395
+ // OU notification ici si logique specifique au controller
396
+
397
+ _logger.LogInformation(
398
+ "User {UserId} created {EntityType} {EntityId}",
399
+ _currentUser.Id, nameof({Entity}), entity.Id);
400
+
401
+ return CreatedAtAction(
402
+ nameof(GetById),
403
+ new { id = entity.Id },
404
+ entity);
405
+ }
406
+ ```
407
+
408
+ ### Template Hook (Frontend)
409
+
410
+ ```typescript
411
+ // hooks/use{Module}Notifications.ts
412
+
413
+ import { useSignalR } from '@/hooks/useSignalR';
414
+ import { useQueryClient } from '@tanstack/react-query';
415
+ import { toast } from 'sonner';
416
+
417
+ export function use{Module}Notifications() {
418
+ const queryClient = useQueryClient();
419
+
420
+ useSignalR({
421
+ onNotification: (notification) => {
422
+ // Filtrer les notifications de ce module
423
+ if (notification.relatedEntityType === '{Entity}') {
424
+ // Invalider les queries pour refresh
425
+ queryClient.invalidateQueries(['{module}']);
426
+
427
+ // Toast avec action
428
+ toast.info(notification.title, {
429
+ description: notification.message,
430
+ action: notification.actionUrl ? {
431
+ label: 'Voir',
432
+ onClick: () => navigate(notification.actionUrl),
433
+ } : undefined,
434
+ });
435
+ }
436
+ },
437
+ });
438
+ }
439
+ ```
440
+
441
+ ---
442
+
443
+ ## CHECKLIST INTEGRATION
444
+
445
+ ```
446
+ □ Type de notification identifie (existant ou nouveau)
447
+ □ Si nouveau type: ajoute a NotificationType enum
448
+ □ Service injecte: INotificationService
449
+ □ Notification envoyee avec tous les champs:
450
+ □ userId (destinataire)
451
+ □ type (NotificationType)
452
+ □ title (titre court)
453
+ □ message (description)
454
+ □ relatedEntityType (optionnel)
455
+ □ relatedEntityId (optionnel)
456
+ □ actionUrl (optionnel)
457
+ □ Logging ajoute (LogInformation)
458
+ □ Frontend: useSignalR hook configure
459
+ □ Frontend: toast ou UI update sur reception
460
+ □ Si email requis: workflow configure
461
+ ```
462
+
463
+ ---
464
+
465
+ ## PATTERNS AVANCES
466
+
467
+ ### Pattern: Notification avec Delai (SLA)
468
+
469
+ ```csharp
470
+ // Pour les SLA warnings, utiliser un job Hangfire
471
+ public class SlaNotificationJob
472
+ {
473
+ public async Task CheckSlaBreaches()
474
+ {
475
+ var tickets = await _context.Tickets
476
+ .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30))
477
+ .Where(t => !t.SlaWarningNotificationSent)
478
+ .ToListAsync();
479
+
480
+ foreach (var ticket in tickets)
481
+ {
482
+ await _notificationService.SendNotificationAsync(
483
+ ticket.AssignedToId ?? ticket.CreatedById,
484
+ NotificationType.SlaWarning,
485
+ "SLA proche expiration",
486
+ $"Le ticket #{ticket.Number} expire dans 30 minutes",
487
+ relatedEntityType: "Ticket",
488
+ relatedEntityId: ticket.Id,
489
+ actionUrl: $"/support/tickets/{ticket.Id}");
490
+
491
+ ticket.SlaWarningNotificationSent = true;
492
+ }
493
+
494
+ await _context.SaveChangesAsync();
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### Pattern: Notification Groupee
500
+
501
+ ```csharp
502
+ // Pour eviter le spam, grouper les notifications similaires
503
+ public async Task SendBatchNotification(
504
+ Guid userId,
505
+ NotificationType type,
506
+ List<EntitySummary> entities)
507
+ {
508
+ if (entities.Count == 1)
509
+ {
510
+ await _notificationService.SendNotificationAsync(
511
+ userId, type,
512
+ $"Nouveau {entities[0].Type}",
513
+ entities[0].Description,
514
+ relatedEntityType: entities[0].Type,
515
+ relatedEntityId: entities[0].Id);
516
+ }
517
+ else
518
+ {
519
+ await _notificationService.SendNotificationAsync(
520
+ userId, type,
521
+ $"{entities.Count} nouveaux elements",
522
+ $"Vous avez {entities.Count} nouveaux {entities[0].Type}s a traiter",
523
+ actionUrl: "/dashboard/pending");
524
+ }
525
+ }
526
+ ```
527
+
528
+ ---
529
+
530
+ ## REGLES ABSOLUES
531
+
532
+ 1. **TOUJOURS** utiliser INotificationService (jamais acces DB direct)
533
+ 2. **TOUJOURS** utiliser les types NotificationType enum
534
+ 3. **TOUJOURS** inclure le context (relatedEntityType/Id) quand applicable
535
+ 4. **TOUJOURS** ajouter actionUrl pour navigation
536
+ 5. **TOUJOURS** logger les notifications envoyees
537
+ 6. **TOUJOURS** respecter les preferences utilisateur
538
+ 7. **JAMAIS** envoyer de notification sans destinataire valide
539
+ 8. **JAMAIS** hardcoder les messages (utiliser i18n ou templates)
540
+ 9. **JAMAIS** spam l'utilisateur (grouper les notifications similaires)
541
+ 10. **JAMAIS** oublier le CancellationToken
542
+
543
+ ---
544
+
545
+ ## FICHIERS CLES
546
+
547
+ | Fichier | Role |
548
+ |---------|------|
549
+ | `Domain/Support/Notification.cs` | Entite notification |
550
+ | `Domain/Support/Enums/NotificationType.cs` | Types de notification |
551
+ | `Application/Common/Interfaces/INotificationService.cs` | Interface service |
552
+ | `Infrastructure/Services/Support/NotificationService.cs` | Implementation |
553
+ | `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
554
+ | `web/src/hooks/useSignalR.ts` | Hook frontend |
555
+ | `web/src/components/notifications/NotificationBell.tsx` | UI Component |