@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.
- package/.documentation/agents.html +916 -916
- package/.documentation/apex.html +1018 -1018
- package/.documentation/business-analyse.html +1501 -1501
- package/.documentation/commands.html +680 -680
- package/.documentation/css/styles.css +2168 -2168
- package/.documentation/efcore.html +2505 -2505
- package/.documentation/gitflow.html +2618 -2618
- package/.documentation/hooks.html +413 -413
- package/.documentation/index.html +323 -323
- package/.documentation/installation.html +462 -462
- package/.documentation/js/app.js +794 -794
- package/.documentation/test-web.html +513 -513
- package/dist/index.js +807 -277
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/conflicts.md +44 -17
- package/templates/agents/efcore/db-status.md +27 -6
- package/templates/agents/efcore/scan.md +43 -13
- package/templates/commands/ai-prompt.md +315 -315
- package/templates/commands/application/create.md +362 -362
- package/templates/commands/controller/create.md +216 -216
- package/templates/commands/controller.md +59 -0
- package/templates/commands/documentation/module.md +202 -202
- package/templates/commands/efcore/_env-check.md +153 -153
- package/templates/commands/efcore/conflicts.md +109 -192
- package/templates/commands/efcore/db-status.md +101 -89
- package/templates/commands/efcore/migration.md +23 -11
- package/templates/commands/efcore/scan.md +115 -119
- package/templates/commands/efcore.md +54 -6
- package/templates/commands/feature-full.md +267 -267
- package/templates/commands/gitflow/11-finish.md +145 -11
- package/templates/commands/gitflow/13-sync.md +216 -216
- package/templates/commands/gitflow/14-rebase.md +251 -251
- package/templates/commands/gitflow/2-status.md +120 -10
- package/templates/commands/gitflow/3-commit.md +150 -0
- package/templates/commands/gitflow/7-pull-request.md +134 -5
- package/templates/commands/gitflow/9-merge.md +142 -1
- package/templates/commands/implement.md +663 -663
- package/templates/commands/init.md +562 -0
- package/templates/commands/mcp-integration.md +330 -0
- package/templates/commands/notification.md +129 -129
- package/templates/commands/validate.md +233 -0
- package/templates/commands/workflow.md +193 -193
- package/templates/skills/ai-prompt/SKILL.md +778 -778
- package/templates/skills/application/SKILL.md +563 -563
- package/templates/skills/application/templates-backend.md +450 -450
- package/templates/skills/application/templates-frontend.md +531 -531
- package/templates/skills/application/templates-i18n.md +520 -520
- package/templates/skills/application/templates-seed.md +647 -647
- package/templates/skills/controller/SKILL.md +240 -240
- package/templates/skills/controller/postman-templates.md +614 -614
- package/templates/skills/controller/templates.md +1468 -1468
- package/templates/skills/documentation/SKILL.md +133 -133
- package/templates/skills/documentation/templates.md +476 -476
- package/templates/skills/feature-full/SKILL.md +838 -838
- package/templates/skills/notification/SKILL.md +555 -555
- package/templates/skills/ui-components/SKILL.md +870 -870
- package/templates/skills/workflow/SKILL.md +582 -582
|
@@ -1,838 +1,838 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: feature-full
|
|
3
|
-
description: |
|
|
4
|
-
Cree une feature complete SmartStack avec toutes les integrations.
|
|
5
|
-
Utiliser ce skill quand:
|
|
6
|
-
- L'utilisateur veut creer une feature complete de A a Z
|
|
7
|
-
- L'utilisateur mentionne "oneshot", "feature complete", "full-stack"
|
|
8
|
-
- Creation d'un module avec notifications, workflows et/ou IA
|
|
9
|
-
- Besoin d'une experience utilisateur complete
|
|
10
|
-
Scope: Domain → Application → Infrastructure → API → Web + Notifications + Workflows + AI
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Skill Feature Full SmartStack
|
|
14
|
-
|
|
15
|
-
> **OneShot Development:** Ce skill orchestre tous les autres skills pour creer
|
|
16
|
-
> une feature complete avec la meilleure experience utilisateur possible.
|
|
17
|
-
|
|
18
|
-
## QUAND CE SKILL S'ACTIVE
|
|
19
|
-
|
|
20
|
-
Claude invoque automatiquement ce skill quand il detecte :
|
|
21
|
-
|
|
22
|
-
| Declencheur | Exemple |
|
|
23
|
-
|-------------|---------|
|
|
24
|
-
| Feature complete | "Cree un module de gestion des produits" |
|
|
25
|
-
| OneShot | "Implemente la feature de A a Z" |
|
|
26
|
-
| Full-stack | "Cree le backend et le frontend pour..." |
|
|
27
|
-
| UX focus | "Je veux une experience utilisateur complete" |
|
|
28
|
-
| Integration | "Avec notifications et emails automatiques" |
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## ARCHITECTURE FEATURE COMPLETE
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
36
|
-
│ FEATURE FULL-STACK COMPLETE │
|
|
37
|
-
├─────────────────────────────────────────────────────────────────────────────┤
|
|
38
|
-
│ │
|
|
39
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
40
|
-
│ │ DOMAIN LAYER │ │
|
|
41
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
42
|
-
│ │ │ Entity │ │ Enums │ │ Events │ │ Factory │ │ Specs │ │ │
|
|
43
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
44
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
45
|
-
│ │ │
|
|
46
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
47
|
-
│ │ APPLICATION LAYER │ │
|
|
48
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
49
|
-
│ │ │Interface│ │ DTOs │ │Commands │ │Validators│ │ │
|
|
50
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
51
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
52
|
-
│ │ │
|
|
53
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
54
|
-
│ │ INFRASTRUCTURE LAYER │ │
|
|
55
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
56
|
-
│ │ │ EF Core │ │ Service │ │ Seed │ │ SignalR │ │ Email │ │ │
|
|
57
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
58
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
59
|
-
│ │ │
|
|
60
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
61
|
-
│ │ API LAYER │ │
|
|
62
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
63
|
-
│ │ │Controller│ │Authorize │ │ Swagger │ │Postman │ │ │
|
|
64
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
65
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
66
|
-
│ │ │
|
|
67
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
68
|
-
│ │ WEB LAYER │ │
|
|
69
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
70
|
-
│ │ │ Pages │ │ Hooks │ │ i18n │ │ API │ │ Routes │ │ │
|
|
71
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
72
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
73
|
-
│ │ │
|
|
74
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
75
|
-
│ │ INTEGRATIONS │ │
|
|
76
|
-
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
77
|
-
│ │ │NOTIFICATIONS│ │ WORKFLOWS │ │ AI │ │ │
|
|
78
|
-
│ │ │ In-App │ │ Emails │ │ Analysis │ │ │
|
|
79
|
-
│ │ │ Real-time │ │ Webhooks │ │ Generation │ │ │
|
|
80
|
-
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
81
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
82
|
-
│ │
|
|
83
|
-
└─────────────────────────────────────────────────────────────────────────────┘
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## WORKFLOW DE CREATION
|
|
89
|
-
|
|
90
|
-
### PHASE 1: ANALYSE (5 min)
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
1. IDENTIFIER LE BESOIN
|
|
94
|
-
□ Quel module/fonctionnalite ?
|
|
95
|
-
□ Quelles entites ?
|
|
96
|
-
□ Quelles operations CRUD ?
|
|
97
|
-
□ Quelles regles metier ?
|
|
98
|
-
|
|
99
|
-
2. DEFINIR LES INTEGRATIONS
|
|
100
|
-
□ Notifications requises ?
|
|
101
|
-
→ Quand ? Pour qui ? Quel type ?
|
|
102
|
-
□ Emails automatiques ?
|
|
103
|
-
→ Quels triggers ? Quels templates ?
|
|
104
|
-
□ Assistance IA ?
|
|
105
|
-
→ Quel use case ? Quelle validation ?
|
|
106
|
-
|
|
107
|
-
3. DEFINIR L'UX
|
|
108
|
-
□ Affichage: Cards, Table, Kanban ?
|
|
109
|
-
□ Navigation: Ou dans l'arborescence ?
|
|
110
|
-
□ Permissions: Qui peut faire quoi ?
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### PHASE 2: GENERATION DOMAIN (10 min)
|
|
114
|
-
|
|
115
|
-
```csharp
|
|
116
|
-
// 1. ENTITE DOMAIN
|
|
117
|
-
// src/SmartStack.Domain/{Area}/{Entity}.cs
|
|
118
|
-
|
|
119
|
-
public class {Entity} : BaseEntity, IAuditableEntity
|
|
120
|
-
{
|
|
121
|
-
// Proprietes
|
|
122
|
-
public string Name { get; private set; }
|
|
123
|
-
public string? Description { get; private set; }
|
|
124
|
-
public {Entity}Status Status { get; private set; }
|
|
125
|
-
|
|
126
|
-
// Relations
|
|
127
|
-
public Guid CreatedById { get; private set; }
|
|
128
|
-
public User CreatedBy { get; private set; } = null!;
|
|
129
|
-
|
|
130
|
-
// Audit
|
|
131
|
-
public string? CreatedByName { get; set; }
|
|
132
|
-
public string? UpdatedByName { get; set; }
|
|
133
|
-
|
|
134
|
-
// Private constructor for EF Core
|
|
135
|
-
private {Entity}() { }
|
|
136
|
-
|
|
137
|
-
// Factory Method
|
|
138
|
-
public static {Entity} Create(
|
|
139
|
-
string name,
|
|
140
|
-
string? description,
|
|
141
|
-
Guid createdById)
|
|
142
|
-
{
|
|
143
|
-
if (string.IsNullOrWhiteSpace(name))
|
|
144
|
-
throw new DomainException("{Entity} name is required");
|
|
145
|
-
|
|
146
|
-
return new {Entity}
|
|
147
|
-
{
|
|
148
|
-
Id = Guid.NewGuid(),
|
|
149
|
-
Name = name,
|
|
150
|
-
Description = description,
|
|
151
|
-
Status = {Entity}Status.Active,
|
|
152
|
-
CreatedById = createdById,
|
|
153
|
-
CreatedAt = DateTime.UtcNow
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Behaviors
|
|
158
|
-
public void Update(string name, string? description)
|
|
159
|
-
{
|
|
160
|
-
Name = name ?? throw new DomainException("Name is required");
|
|
161
|
-
Description = description;
|
|
162
|
-
UpdatedAt = DateTime.UtcNow;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
public void Activate() => Status = {Entity}Status.Active;
|
|
166
|
-
public void Deactivate() => Status = {Entity}Status.Inactive;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// 2. ENUM
|
|
170
|
-
// src/SmartStack.Domain/{Area}/Enums/{Entity}Status.cs
|
|
171
|
-
|
|
172
|
-
public enum {Entity}Status
|
|
173
|
-
{
|
|
174
|
-
Active = 0,
|
|
175
|
-
Inactive = 1,
|
|
176
|
-
Archived = 2
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### PHASE 3: GENERATION APPLICATION (10 min)
|
|
181
|
-
|
|
182
|
-
```csharp
|
|
183
|
-
// 1. INTERFACE SERVICE
|
|
184
|
-
// src/SmartStack.Application/Common/Interfaces/I{Entity}Service.cs
|
|
185
|
-
|
|
186
|
-
public interface I{Entity}Service
|
|
187
|
-
{
|
|
188
|
-
Task<PagedResult<{Entity}Dto>> GetAllAsync(
|
|
189
|
-
int page, int pageSize, string? search, CancellationToken ct);
|
|
190
|
-
Task<{Entity}Dto?> GetByIdAsync(Guid id, CancellationToken ct);
|
|
191
|
-
Task<{Entity}Dto> CreateAsync(Create{Entity}Request request, CancellationToken ct);
|
|
192
|
-
Task<{Entity}Dto> UpdateAsync(Guid id, Update{Entity}Request request, CancellationToken ct);
|
|
193
|
-
Task DeleteAsync(Guid id, CancellationToken ct);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// 2. DTOs
|
|
197
|
-
// src/SmartStack.Application/{Area}/DTOs/{Entity}Dto.cs
|
|
198
|
-
|
|
199
|
-
public record {Entity}Dto(
|
|
200
|
-
Guid Id,
|
|
201
|
-
string Name,
|
|
202
|
-
string? Description,
|
|
203
|
-
string Status,
|
|
204
|
-
DateTime CreatedAt,
|
|
205
|
-
string? CreatedByName);
|
|
206
|
-
|
|
207
|
-
public record Create{Entity}Request(
|
|
208
|
-
string Name,
|
|
209
|
-
string? Description);
|
|
210
|
-
|
|
211
|
-
public record Update{Entity}Request(
|
|
212
|
-
string Name,
|
|
213
|
-
string? Description);
|
|
214
|
-
|
|
215
|
-
// 3. PERMISSIONS
|
|
216
|
-
// src/SmartStack.Application/Common/Authorization/Permissions.cs
|
|
217
|
-
|
|
218
|
-
public static class {Area}
|
|
219
|
-
{
|
|
220
|
-
public static class {Module}
|
|
221
|
-
{
|
|
222
|
-
public const string View = "{context}.{area}.{module}.read";
|
|
223
|
-
public const string Create = "{context}.{area}.{module}.create";
|
|
224
|
-
public const string Update = "{context}.{area}.{module}.update";
|
|
225
|
-
public const string Delete = "{context}.{area}.{module}.delete";
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### PHASE 4: GENERATION INFRASTRUCTURE (15 min)
|
|
231
|
-
|
|
232
|
-
```csharp
|
|
233
|
-
// 1. CONFIGURATION EF CORE
|
|
234
|
-
// src/SmartStack.Infrastructure/Persistence/Configurations/{Area}/{Entity}Configuration.cs
|
|
235
|
-
|
|
236
|
-
public class {Entity}Configuration : IEntityTypeConfiguration<{Entity}>
|
|
237
|
-
{
|
|
238
|
-
public void Configure(EntityTypeBuilder<{Entity}> builder)
|
|
239
|
-
{
|
|
240
|
-
builder.ToTable("{Entity}s", "{schema}");
|
|
241
|
-
|
|
242
|
-
builder.HasKey(x => x.Id);
|
|
243
|
-
|
|
244
|
-
builder.Property(x => x.Name)
|
|
245
|
-
.HasMaxLength(200)
|
|
246
|
-
.IsRequired();
|
|
247
|
-
|
|
248
|
-
builder.Property(x => x.Description)
|
|
249
|
-
.HasMaxLength(2000);
|
|
250
|
-
|
|
251
|
-
builder.HasIndex(x => x.Name);
|
|
252
|
-
builder.HasIndex(x => x.Status);
|
|
253
|
-
builder.HasIndex(x => x.CreatedById);
|
|
254
|
-
|
|
255
|
-
// Navigation
|
|
256
|
-
builder.HasOne(x => x.CreatedBy)
|
|
257
|
-
.WithMany()
|
|
258
|
-
.HasForeignKey(x => x.CreatedById)
|
|
259
|
-
.OnDelete(DeleteBehavior.Restrict);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// 2. SERVICE IMPLEMENTATION
|
|
264
|
-
// src/SmartStack.Infrastructure/Services/{Area}/{Entity}Service.cs
|
|
265
|
-
|
|
266
|
-
public class {Entity}Service : I{Entity}Service
|
|
267
|
-
{
|
|
268
|
-
private readonly IApplicationDbContext _context;
|
|
269
|
-
private readonly ICurrentUserService _currentUser;
|
|
270
|
-
private readonly INotificationService _notificationService;
|
|
271
|
-
private readonly ILogger<{Entity}Service> _logger;
|
|
272
|
-
|
|
273
|
-
public {Entity}Service(
|
|
274
|
-
IApplicationDbContext context,
|
|
275
|
-
ICurrentUserService currentUser,
|
|
276
|
-
INotificationService notificationService,
|
|
277
|
-
ILogger<{Entity}Service> logger)
|
|
278
|
-
{
|
|
279
|
-
_context = context;
|
|
280
|
-
_currentUser = currentUser;
|
|
281
|
-
_notificationService = notificationService;
|
|
282
|
-
_logger = logger;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
public async Task<{Entity}Dto> CreateAsync(
|
|
286
|
-
Create{Entity}Request request,
|
|
287
|
-
CancellationToken ct)
|
|
288
|
-
{
|
|
289
|
-
var entity = {Entity}.Create(
|
|
290
|
-
request.Name,
|
|
291
|
-
request.Description,
|
|
292
|
-
_currentUser.Id);
|
|
293
|
-
|
|
294
|
-
entity.CreatedByName = _currentUser.DisplayName;
|
|
295
|
-
|
|
296
|
-
_context.{Entity}s.Add(entity);
|
|
297
|
-
await _context.SaveChangesAsync(ct);
|
|
298
|
-
|
|
299
|
-
// NOTIFICATION
|
|
300
|
-
await _notificationService.SendNotificationAsync(
|
|
301
|
-
_currentUser.Id,
|
|
302
|
-
NotificationType.{Entity}Created,
|
|
303
|
-
"{Entity} creee",
|
|
304
|
-
$"{entity.Name} a ete cree avec succes",
|
|
305
|
-
relatedEntityType: nameof({Entity}),
|
|
306
|
-
relatedEntityId: entity.Id,
|
|
307
|
-
actionUrl: $"/{area}/{module}/{entity.Id}",
|
|
308
|
-
cancellationToken: ct);
|
|
309
|
-
|
|
310
|
-
_logger.LogInformation(
|
|
311
|
-
"User {UserId} created {EntityType} {EntityId}: {EntityName}",
|
|
312
|
-
_currentUser.Id, nameof({Entity}), entity.Id, entity.Name);
|
|
313
|
-
|
|
314
|
-
return MapToDto(entity);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// ... autres methodes CRUD avec notifications
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// 3. DEPENDENCY INJECTION
|
|
321
|
-
// Ajouter dans DependencyInjection.cs:
|
|
322
|
-
services.AddScoped<I{Entity}Service, {Entity}Service>();
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### PHASE 5: GENERATION API (10 min)
|
|
326
|
-
|
|
327
|
-
```csharp
|
|
328
|
-
// src/SmartStack.Api/Controllers/{Area}/{Entity}Controller.cs
|
|
329
|
-
|
|
330
|
-
[ApiController]
|
|
331
|
-
[Route("api/{area}/{module}")]
|
|
332
|
-
[Authorize]
|
|
333
|
-
public class {Entity}Controller : ControllerBase
|
|
334
|
-
{
|
|
335
|
-
private readonly I{Entity}Service _service;
|
|
336
|
-
private readonly ILogger<{Entity}Controller> _logger;
|
|
337
|
-
|
|
338
|
-
public {Entity}Controller(
|
|
339
|
-
I{Entity}Service service,
|
|
340
|
-
ILogger<{Entity}Controller> logger)
|
|
341
|
-
{
|
|
342
|
-
_service = service;
|
|
343
|
-
_logger = logger;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
[HttpGet]
|
|
347
|
-
[RequirePermission(Permissions.{Area}.{Module}.View)]
|
|
348
|
-
[ProducesResponseType(typeof(PagedResult<{Entity}Dto>), StatusCodes.Status200OK)]
|
|
349
|
-
public async Task<ActionResult<PagedResult<{Entity}Dto>>> GetAll(
|
|
350
|
-
[FromQuery] int page = 1,
|
|
351
|
-
[FromQuery] int pageSize = 20,
|
|
352
|
-
[FromQuery] string? search = null,
|
|
353
|
-
CancellationToken ct = default)
|
|
354
|
-
{
|
|
355
|
-
var result = await _service.GetAllAsync(page, pageSize, search, ct);
|
|
356
|
-
return Ok(result);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
[HttpGet("{id:guid}")]
|
|
360
|
-
[RequirePermission(Permissions.{Area}.{Module}.View)]
|
|
361
|
-
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status200OK)]
|
|
362
|
-
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
363
|
-
public async Task<ActionResult<{Entity}Dto>> GetById(Guid id, CancellationToken ct)
|
|
364
|
-
{
|
|
365
|
-
var result = await _service.GetByIdAsync(id, ct);
|
|
366
|
-
return result is null ? NotFound() : Ok(result);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
[HttpPost]
|
|
370
|
-
[RequirePermission(Permissions.{Area}.{Module}.Create)]
|
|
371
|
-
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
|
|
372
|
-
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
373
|
-
public async Task<ActionResult<{Entity}Dto>> Create(
|
|
374
|
-
[FromBody] Create{Entity}Request request,
|
|
375
|
-
CancellationToken ct)
|
|
376
|
-
{
|
|
377
|
-
var result = await _service.CreateAsync(request, ct);
|
|
378
|
-
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
[HttpPut("{id:guid}")]
|
|
382
|
-
[RequirePermission(Permissions.{Area}.{Module}.Update)]
|
|
383
|
-
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status200OK)]
|
|
384
|
-
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
385
|
-
public async Task<ActionResult<{Entity}Dto>> Update(
|
|
386
|
-
Guid id,
|
|
387
|
-
[FromBody] Update{Entity}Request request,
|
|
388
|
-
CancellationToken ct)
|
|
389
|
-
{
|
|
390
|
-
var result = await _service.UpdateAsync(id, request, ct);
|
|
391
|
-
return Ok(result);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
[HttpDelete("{id:guid}")]
|
|
395
|
-
[RequirePermission(Permissions.{Area}.{Module}.Delete)]
|
|
396
|
-
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
397
|
-
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
398
|
-
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
|
399
|
-
{
|
|
400
|
-
await _service.DeleteAsync(id, ct);
|
|
401
|
-
return NoContent();
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### PHASE 6: GENERATION FRONTEND (20 min)
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
// 1. API SERVICE
|
|
410
|
-
// web/src/services/api/{module}Api.ts
|
|
411
|
-
|
|
412
|
-
import { apiClient } from './apiClient';
|
|
413
|
-
import type { PagedResult, {Entity}Dto, Create{Entity}Request, Update{Entity}Request } from '@/types';
|
|
414
|
-
|
|
415
|
-
export const {module}Api = {
|
|
416
|
-
getAll: (page = 1, pageSize = 20, search?: string) =>
|
|
417
|
-
apiClient.get<PagedResult<{Entity}Dto>>('/{area}/{module}', {
|
|
418
|
-
params: { page, pageSize, search }
|
|
419
|
-
}),
|
|
420
|
-
|
|
421
|
-
getById: (id: string) =>
|
|
422
|
-
apiClient.get<{Entity}Dto>(`/{area}/{module}/${id}`),
|
|
423
|
-
|
|
424
|
-
create: (data: Create{Entity}Request) =>
|
|
425
|
-
apiClient.post<{Entity}Dto>('/{area}/{module}', data),
|
|
426
|
-
|
|
427
|
-
update: (id: string, data: Update{Entity}Request) =>
|
|
428
|
-
apiClient.put<{Entity}Dto>(`/{area}/{module}/${id}`, data),
|
|
429
|
-
|
|
430
|
-
delete: (id: string) =>
|
|
431
|
-
apiClient.delete(`/{area}/{module}/${id}`),
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
// 2. HOOK AVEC SIGNALR
|
|
435
|
-
// web/src/hooks/use{Module}.ts
|
|
436
|
-
|
|
437
|
-
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
438
|
-
import { {module}Api } from '@/services/api/{module}Api';
|
|
439
|
-
import { useSignalR } from '@/hooks/useSignalR';
|
|
440
|
-
import { toast } from 'sonner';
|
|
441
|
-
|
|
442
|
-
export function use{Module}() {
|
|
443
|
-
const queryClient = useQueryClient();
|
|
444
|
-
|
|
445
|
-
// Real-time updates
|
|
446
|
-
useSignalR({
|
|
447
|
-
onNotification: (notification) => {
|
|
448
|
-
if (notification.relatedEntityType === '{Entity}') {
|
|
449
|
-
queryClient.invalidateQueries(['{module}']);
|
|
450
|
-
toast.info(notification.title, {
|
|
451
|
-
description: notification.message,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
const {module}Query = useQuery({
|
|
458
|
-
queryKey: ['{module}'],
|
|
459
|
-
queryFn: () => {module}Api.getAll(),
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
const createMutation = useMutation({
|
|
463
|
-
mutationFn: {module}Api.create,
|
|
464
|
-
onSuccess: () => {
|
|
465
|
-
queryClient.invalidateQueries(['{module}']);
|
|
466
|
-
toast.success('{Entity} creee');
|
|
467
|
-
},
|
|
468
|
-
onError: (error) => {
|
|
469
|
-
toast.error('Erreur lors de la creation');
|
|
470
|
-
},
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
const updateMutation = useMutation({
|
|
474
|
-
mutationFn: ({ id, data }: { id: string; data: Update{Entity}Request }) =>
|
|
475
|
-
{module}Api.update(id, data),
|
|
476
|
-
onSuccess: () => {
|
|
477
|
-
queryClient.invalidateQueries(['{module}']);
|
|
478
|
-
toast.success('{Entity} mise a jour');
|
|
479
|
-
},
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
const deleteMutation = useMutation({
|
|
483
|
-
mutationFn: {module}Api.delete,
|
|
484
|
-
onSuccess: () => {
|
|
485
|
-
queryClient.invalidateQueries(['{module}']);
|
|
486
|
-
toast.success('{Entity} supprimee');
|
|
487
|
-
},
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
return {
|
|
491
|
-
{module}: {module}Query.data?.items ?? [],
|
|
492
|
-
isLoading: {module}Query.isLoading,
|
|
493
|
-
error: {module}Query.error,
|
|
494
|
-
create: createMutation.mutate,
|
|
495
|
-
update: updateMutation.mutate,
|
|
496
|
-
delete: deleteMutation.mutate,
|
|
497
|
-
isCreating: createMutation.isPending,
|
|
498
|
-
isUpdating: updateMutation.isPending,
|
|
499
|
-
isDeleting: deleteMutation.isPending,
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// 3. PAGE PRINCIPALE
|
|
504
|
-
// web/src/pages/{area}/{module}/{Module}Page.tsx
|
|
505
|
-
|
|
506
|
-
import { useState } from 'react';
|
|
507
|
-
import { Plus, Search } from 'lucide-react';
|
|
508
|
-
import { use{Module} } from '@/hooks/use{Module}';
|
|
509
|
-
import { EntityCard } from '@/components/ui/EntityCard';
|
|
510
|
-
import { {Module}Form } from './components/{Module}Form';
|
|
511
|
-
import { useTranslation } from 'react-i18next';
|
|
512
|
-
|
|
513
|
-
export function {Module}Page() {
|
|
514
|
-
const { t } = useTranslation('{module}');
|
|
515
|
-
const { {module}, isLoading, create, delete: remove } = use{Module}();
|
|
516
|
-
const [showForm, setShowForm] = useState(false);
|
|
517
|
-
const [search, setSearch] = useState('');
|
|
518
|
-
|
|
519
|
-
const filtered = {module}.filter(item =>
|
|
520
|
-
item.name.toLowerCase().includes(search.toLowerCase())
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
return (
|
|
524
|
-
<div className="space-y-6">
|
|
525
|
-
{/* Header */}
|
|
526
|
-
<div className="flex items-center justify-between">
|
|
527
|
-
<div>
|
|
528
|
-
<h1 className="text-2xl font-bold">{t('title')}</h1>
|
|
529
|
-
<p className="text-[var(--text-secondary)]">{t('subtitle')}</p>
|
|
530
|
-
</div>
|
|
531
|
-
<button
|
|
532
|
-
onClick={() => setShowForm(true)}
|
|
533
|
-
className="inline-flex items-center gap-2 px-4 py-2 bg-[var(--color-accent-600)] text-white rounded-lg"
|
|
534
|
-
>
|
|
535
|
-
<Plus className="w-4 h-4" />
|
|
536
|
-
{t('create')}
|
|
537
|
-
</button>
|
|
538
|
-
</div>
|
|
539
|
-
|
|
540
|
-
{/* Search */}
|
|
541
|
-
<div className="relative">
|
|
542
|
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--text-tertiary)]" />
|
|
543
|
-
<input
|
|
544
|
-
type="text"
|
|
545
|
-
placeholder={t('search')}
|
|
546
|
-
value={search}
|
|
547
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
548
|
-
className="w-full pl-10 pr-4 py-2 border rounded-lg"
|
|
549
|
-
/>
|
|
550
|
-
</div>
|
|
551
|
-
|
|
552
|
-
{/* Grid */}
|
|
553
|
-
{isLoading ? (
|
|
554
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
555
|
-
{Array.from({ length: 6 }).map((_, i) => (
|
|
556
|
-
<div key={i} className="h-48 bg-[var(--bg-secondary)] animate-pulse rounded-lg" />
|
|
557
|
-
))}
|
|
558
|
-
</div>
|
|
559
|
-
) : filtered.length === 0 ? (
|
|
560
|
-
<div className="text-center py-12">
|
|
561
|
-
<p className="text-[var(--text-secondary)]">{t('empty')}</p>
|
|
562
|
-
</div>
|
|
563
|
-
) : (
|
|
564
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
565
|
-
{filtered.map((item) => (
|
|
566
|
-
<EntityCard
|
|
567
|
-
key={item.id}
|
|
568
|
-
avatar={{ letter: item.name[0], color: 'var(--color-accent-500)' }}
|
|
569
|
-
title={item.name}
|
|
570
|
-
subtitle={item.status}
|
|
571
|
-
description={item.description}
|
|
572
|
-
actions={[
|
|
573
|
-
{ label: t('view'), onClick: () => navigate(`/${area}/{module}/${item.id}`), variant: 'primary' },
|
|
574
|
-
{ label: t('delete'), onClick: () => remove(item.id), variant: 'ghost' },
|
|
575
|
-
]}
|
|
576
|
-
/>
|
|
577
|
-
))}
|
|
578
|
-
</div>
|
|
579
|
-
)}
|
|
580
|
-
|
|
581
|
-
{/* Form Modal */}
|
|
582
|
-
{showForm && (
|
|
583
|
-
<{Module}Form
|
|
584
|
-
onSubmit={(data) => {
|
|
585
|
-
create(data);
|
|
586
|
-
setShowForm(false);
|
|
587
|
-
}}
|
|
588
|
-
onClose={() => setShowForm(false)}
|
|
589
|
-
/>
|
|
590
|
-
)}
|
|
591
|
-
</div>
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// 4. i18n
|
|
596
|
-
// web/src/i18n/locales/fr/{module}.json
|
|
597
|
-
|
|
598
|
-
{
|
|
599
|
-
"title": "{Module}",
|
|
600
|
-
"subtitle": "Gerez vos {module}s",
|
|
601
|
-
"create": "Creer",
|
|
602
|
-
"search": "Rechercher...",
|
|
603
|
-
"empty": "Aucun {module} trouve",
|
|
604
|
-
"view": "Voir",
|
|
605
|
-
"edit": "Modifier",
|
|
606
|
-
"delete": "Supprimer",
|
|
607
|
-
"form": {
|
|
608
|
-
"name": "Nom",
|
|
609
|
-
"namePlaceholder": "Entrez le nom",
|
|
610
|
-
"description": "Description",
|
|
611
|
-
"descriptionPlaceholder": "Entrez une description",
|
|
612
|
-
"submit": "Enregistrer",
|
|
613
|
-
"cancel": "Annuler"
|
|
614
|
-
},
|
|
615
|
-
"notifications": {
|
|
616
|
-
"created": "{Module} creee avec succes",
|
|
617
|
-
"updated": "{Module} mise a jour",
|
|
618
|
-
"deleted": "{Module} supprimee"
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// 5. ROUTES
|
|
623
|
-
// App.tsx - Ajouter:
|
|
624
|
-
<Route path="{area}">
|
|
625
|
-
<Route index element={<Navigate to="{module}" replace />} />
|
|
626
|
-
<Route path="{module}">
|
|
627
|
-
<Route index element={<{Module}Page />} />
|
|
628
|
-
<Route path=":id" element={<{Module}DetailPage />} />
|
|
629
|
-
</Route>
|
|
630
|
-
</Route>
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
### PHASE 7: INTEGRATIONS (15 min)
|
|
634
|
-
|
|
635
|
-
#### 7.1 Notifications
|
|
636
|
-
|
|
637
|
-
```csharp
|
|
638
|
-
// Dans le service, ajouter les notifications appropriees:
|
|
639
|
-
|
|
640
|
-
// Creation
|
|
641
|
-
await _notificationService.SendNotificationAsync(
|
|
642
|
-
_currentUser.Id,
|
|
643
|
-
NotificationType.{Entity}Created,
|
|
644
|
-
t["notifications.created.title"],
|
|
645
|
-
t["notifications.created.message", entity.Name],
|
|
646
|
-
relatedEntityType: nameof({Entity}),
|
|
647
|
-
relatedEntityId: entity.Id,
|
|
648
|
-
actionUrl: $"/{area}/{module}/{entity.Id}");
|
|
649
|
-
|
|
650
|
-
// Si assignation
|
|
651
|
-
if (entity.AssignedToId.HasValue)
|
|
652
|
-
{
|
|
653
|
-
await _notificationService.SendNotificationAsync(
|
|
654
|
-
entity.AssignedToId.Value,
|
|
655
|
-
NotificationType.{Entity}Assigned,
|
|
656
|
-
"{Entity} assignee",
|
|
657
|
-
$"La {entity.Name} vous a ete assignee",
|
|
658
|
-
relatedEntityType: nameof({Entity}),
|
|
659
|
-
relatedEntityId: entity.Id,
|
|
660
|
-
actionUrl: $"/{area}/{module}/{entity.Id}");
|
|
661
|
-
}
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
#### 7.2 Workflow Email
|
|
665
|
-
|
|
666
|
-
```csharp
|
|
667
|
-
// 1. Creer le trigger
|
|
668
|
-
// Dans WorkflowTriggerConfiguration.cs:
|
|
669
|
-
new
|
|
670
|
-
{
|
|
671
|
-
Id = Guid.Parse("NEW-TRIGGER-GUID"),
|
|
672
|
-
Code = "{entity}.created",
|
|
673
|
-
Name = "{Entity} Created",
|
|
674
|
-
AvailableVariablesJson = "[{\"Name\":\"entityId\"},{\"Name\":\"entityName\"},{\"Name\":\"creatorEmail\"}]",
|
|
675
|
-
IsActive = true,
|
|
676
|
-
CreatedAt = seedDate
|
|
677
|
-
},
|
|
678
|
-
|
|
679
|
-
// 2. Creer le workflow
|
|
680
|
-
// Dans WorkflowConfiguration.cs:
|
|
681
|
-
new
|
|
682
|
-
{
|
|
683
|
-
Id = Guid.Parse("NEW-WORKFLOW-GUID"),
|
|
684
|
-
Code = "{entity}-created-notification",
|
|
685
|
-
Name = "{Entity} Created Notification",
|
|
686
|
-
TriggerId = Guid.Parse("TRIGGER-GUID"),
|
|
687
|
-
IsActive = true,
|
|
688
|
-
IsSystem = true,
|
|
689
|
-
Priority = 10,
|
|
690
|
-
CreatedAt = seedDate
|
|
691
|
-
},
|
|
692
|
-
|
|
693
|
-
// 3. Declencher dans le service
|
|
694
|
-
await _workflowService.TriggerAsync(
|
|
695
|
-
"{entity}.created",
|
|
696
|
-
new Dictionary<string, object>
|
|
697
|
-
{
|
|
698
|
-
["entityId"] = entity.Id,
|
|
699
|
-
["entityName"] = entity.Name,
|
|
700
|
-
["creatorEmail"] = _currentUser.Email
|
|
701
|
-
});
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
#### 7.3 AI Integration
|
|
705
|
-
|
|
706
|
-
```csharp
|
|
707
|
-
// Si analyse IA requise:
|
|
708
|
-
public async Task<{Entity}AnalysisResult?> AnalyzeAsync(Guid entityId, CancellationToken ct)
|
|
709
|
-
{
|
|
710
|
-
var entity = await _context.{Entity}s.FindAsync(entityId);
|
|
711
|
-
if (entity == null) return null;
|
|
712
|
-
|
|
713
|
-
var result = await _aiCompletionService
|
|
714
|
-
.ExecutePromptByCodeWithValidationAsync<{Entity}AnalysisResult>(
|
|
715
|
-
"{entity}-analyzer",
|
|
716
|
-
new Dictionary<string, object>
|
|
717
|
-
{
|
|
718
|
-
["entityName"] = entity.Name,
|
|
719
|
-
["entityDescription"] = entity.Description ?? ""
|
|
720
|
-
},
|
|
721
|
-
cancellationToken: ct);
|
|
722
|
-
|
|
723
|
-
if (result.Success && result.IsValid)
|
|
724
|
-
{
|
|
725
|
-
_logger.LogInformation(
|
|
726
|
-
"{Entity} {EntityId} analyzed: {Category}",
|
|
727
|
-
entityId, result.Data.Category);
|
|
728
|
-
return result.Data;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
return null;
|
|
732
|
-
}
|
|
733
|
-
```
|
|
734
|
-
|
|
735
|
-
---
|
|
736
|
-
|
|
737
|
-
## CHECKLIST COMPLETE
|
|
738
|
-
|
|
739
|
-
### Domain Layer
|
|
740
|
-
```
|
|
741
|
-
□ Entite {Entity}.cs creee
|
|
742
|
-
□ Factory Method Create() implemente
|
|
743
|
-
□ Behaviors (Update, Activate, etc.)
|
|
744
|
-
□ Enum {Entity}Status.cs
|
|
745
|
-
□ IAuditableEntity implemente
|
|
746
|
-
```
|
|
747
|
-
|
|
748
|
-
### Application Layer
|
|
749
|
-
```
|
|
750
|
-
□ Interface I{Entity}Service.cs
|
|
751
|
-
□ DTOs: {Entity}Dto, Create/Update Requests
|
|
752
|
-
□ Permissions ajoutees dans Permissions.cs
|
|
753
|
-
□ Permissions ajoutees dans PermissionConfiguration.cs
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
### Infrastructure Layer
|
|
757
|
-
```
|
|
758
|
-
□ {Entity}Configuration.cs (EF Core)
|
|
759
|
-
□ {Entity}Service.cs implementation
|
|
760
|
-
□ Injection DI dans DependencyInjection.cs
|
|
761
|
-
□ DbSet<{Entity}> dans ApplicationDbContext
|
|
762
|
-
□ Migration EF Core creee
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
### API Layer
|
|
766
|
-
```
|
|
767
|
-
□ {Entity}Controller.cs
|
|
768
|
-
□ [Authorize] et [RequirePermission]
|
|
769
|
-
□ [ProducesResponseType] documentes
|
|
770
|
-
□ Logging dans les actions
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
### Web Layer
|
|
774
|
-
```
|
|
775
|
-
□ {module}Api.ts (service API)
|
|
776
|
-
□ use{Module}.ts (hook avec SignalR)
|
|
777
|
-
□ {Module}Page.tsx (page principale)
|
|
778
|
-
□ {Module}Form.tsx (formulaire)
|
|
779
|
-
□ {Module}DetailPage.tsx (detail)
|
|
780
|
-
□ i18n: fr/{module}.json + en/{module}.json
|
|
781
|
-
□ Routes dans App.tsx
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
### Integrations
|
|
785
|
-
```
|
|
786
|
-
□ Notifications:
|
|
787
|
-
□ NotificationType ajoute
|
|
788
|
-
□ SendNotificationAsync dans le service
|
|
789
|
-
□ useSignalR dans le hook
|
|
790
|
-
□ Workflows (si emails requis):
|
|
791
|
-
□ Trigger cree
|
|
792
|
-
□ Workflow cree
|
|
793
|
-
□ TriggerAsync dans le service
|
|
794
|
-
□ AI (si analyse requise):
|
|
795
|
-
□ Prompt cree
|
|
796
|
-
□ OutputSchema cree
|
|
797
|
-
□ ExecutePromptAsync dans le service
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### Validation
|
|
801
|
-
```
|
|
802
|
-
□ dotnet build OK
|
|
803
|
-
□ npm run build OK
|
|
804
|
-
□ npm run lint OK
|
|
805
|
-
□ Tests unitaires
|
|
806
|
-
□ Test manuel E2E
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
---
|
|
810
|
-
|
|
811
|
-
## SKILLS ASSOCIES
|
|
812
|
-
|
|
813
|
-
Ce skill orchestre les skills suivants :
|
|
814
|
-
|
|
815
|
-
| Skill | Phase | Action |
|
|
816
|
-
|-------|-------|--------|
|
|
817
|
-
| `/application` | 2-6 | Creation module full-stack |
|
|
818
|
-
| `/controller` | 5 | Generation controller API |
|
|
819
|
-
| `/notification` | 7.1 | Integration notifications |
|
|
820
|
-
| `/workflow` | 7.2 | Integration workflows/emails |
|
|
821
|
-
| `/ai-prompt` | 7.3 | Integration IA |
|
|
822
|
-
| `/ui-components` | 6 | Composants UI (EntityCard) |
|
|
823
|
-
| `/efcore:migration` | 4 | Migration EF Core |
|
|
824
|
-
|
|
825
|
-
---
|
|
826
|
-
|
|
827
|
-
## REGLES ABSOLUES
|
|
828
|
-
|
|
829
|
-
1. **TOUJOURS** suivre l'architecture en couches
|
|
830
|
-
2. **TOUJOURS** utiliser Factory Methods pour les entites
|
|
831
|
-
3. **TOUJOURS** implementer IAuditableEntity
|
|
832
|
-
4. **TOUJOURS** ajouter les permissions dans les 2 fichiers
|
|
833
|
-
5. **TOUJOURS** utiliser les hooks avec SignalR
|
|
834
|
-
6. **TOUJOURS** internationaliser (4 langues: fr, en, it, de)
|
|
835
|
-
7. **TOUJOURS** logger les operations importantes
|
|
836
|
-
8. **TOUJOURS** documenter l'API avec ProducesResponseType
|
|
837
|
-
9. **JAMAIS** d'acces DB direct depuis le frontend
|
|
838
|
-
10. **JAMAIS** de permissions hardcodees en strings
|
|
1
|
+
---
|
|
2
|
+
name: feature-full
|
|
3
|
+
description: |
|
|
4
|
+
Cree une feature complete SmartStack avec toutes les integrations.
|
|
5
|
+
Utiliser ce skill quand:
|
|
6
|
+
- L'utilisateur veut creer une feature complete de A a Z
|
|
7
|
+
- L'utilisateur mentionne "oneshot", "feature complete", "full-stack"
|
|
8
|
+
- Creation d'un module avec notifications, workflows et/ou IA
|
|
9
|
+
- Besoin d'une experience utilisateur complete
|
|
10
|
+
Scope: Domain → Application → Infrastructure → API → Web + Notifications + Workflows + AI
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Skill Feature Full SmartStack
|
|
14
|
+
|
|
15
|
+
> **OneShot Development:** Ce skill orchestre tous les autres skills pour creer
|
|
16
|
+
> une feature complete avec la meilleure experience utilisateur possible.
|
|
17
|
+
|
|
18
|
+
## QUAND CE SKILL S'ACTIVE
|
|
19
|
+
|
|
20
|
+
Claude invoque automatiquement ce skill quand il detecte :
|
|
21
|
+
|
|
22
|
+
| Declencheur | Exemple |
|
|
23
|
+
|-------------|---------|
|
|
24
|
+
| Feature complete | "Cree un module de gestion des produits" |
|
|
25
|
+
| OneShot | "Implemente la feature de A a Z" |
|
|
26
|
+
| Full-stack | "Cree le backend et le frontend pour..." |
|
|
27
|
+
| UX focus | "Je veux une experience utilisateur complete" |
|
|
28
|
+
| Integration | "Avec notifications et emails automatiques" |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ARCHITECTURE FEATURE COMPLETE
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
36
|
+
│ FEATURE FULL-STACK COMPLETE │
|
|
37
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
38
|
+
│ │
|
|
39
|
+
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
40
|
+
│ │ DOMAIN LAYER │ │
|
|
41
|
+
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
42
|
+
│ │ │ Entity │ │ Enums │ │ Events │ │ Factory │ │ Specs │ │ │
|
|
43
|
+
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
44
|
+
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
45
|
+
│ │ │
|
|
46
|
+
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
47
|
+
│ │ APPLICATION LAYER │ │
|
|
48
|
+
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
49
|
+
│ │ │Interface│ │ DTOs │ │Commands │ │Validators│ │ │
|
|
50
|
+
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
51
|
+
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
52
|
+
│ │ │
|
|
53
|
+
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
54
|
+
│ │ INFRASTRUCTURE LAYER │ │
|
|
55
|
+
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
56
|
+
│ │ │ EF Core │ │ Service │ │ Seed │ │ SignalR │ │ Email │ │ │
|
|
57
|
+
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
58
|
+
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
59
|
+
│ │ │
|
|
60
|
+
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
61
|
+
│ │ API LAYER │ │
|
|
62
|
+
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
63
|
+
│ │ │Controller│ │Authorize │ │ Swagger │ │Postman │ │ │
|
|
64
|
+
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
65
|
+
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
66
|
+
│ │ │
|
|
67
|
+
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
68
|
+
│ │ WEB LAYER │ │
|
|
69
|
+
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
70
|
+
│ │ │ Pages │ │ Hooks │ │ i18n │ │ API │ │ Routes │ │ │
|
|
71
|
+
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
72
|
+
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
73
|
+
│ │ │
|
|
74
|
+
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
75
|
+
│ │ INTEGRATIONS │ │
|
|
76
|
+
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
77
|
+
│ │ │NOTIFICATIONS│ │ WORKFLOWS │ │ AI │ │ │
|
|
78
|
+
│ │ │ In-App │ │ Emails │ │ Analysis │ │ │
|
|
79
|
+
│ │ │ Real-time │ │ Webhooks │ │ Generation │ │ │
|
|
80
|
+
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
81
|
+
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
82
|
+
│ │
|
|
83
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## WORKFLOW DE CREATION
|
|
89
|
+
|
|
90
|
+
### PHASE 1: ANALYSE (5 min)
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
1. IDENTIFIER LE BESOIN
|
|
94
|
+
□ Quel module/fonctionnalite ?
|
|
95
|
+
□ Quelles entites ?
|
|
96
|
+
□ Quelles operations CRUD ?
|
|
97
|
+
□ Quelles regles metier ?
|
|
98
|
+
|
|
99
|
+
2. DEFINIR LES INTEGRATIONS
|
|
100
|
+
□ Notifications requises ?
|
|
101
|
+
→ Quand ? Pour qui ? Quel type ?
|
|
102
|
+
□ Emails automatiques ?
|
|
103
|
+
→ Quels triggers ? Quels templates ?
|
|
104
|
+
□ Assistance IA ?
|
|
105
|
+
→ Quel use case ? Quelle validation ?
|
|
106
|
+
|
|
107
|
+
3. DEFINIR L'UX
|
|
108
|
+
□ Affichage: Cards, Table, Kanban ?
|
|
109
|
+
□ Navigation: Ou dans l'arborescence ?
|
|
110
|
+
□ Permissions: Qui peut faire quoi ?
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### PHASE 2: GENERATION DOMAIN (10 min)
|
|
114
|
+
|
|
115
|
+
```csharp
|
|
116
|
+
// 1. ENTITE DOMAIN
|
|
117
|
+
// src/SmartStack.Domain/{Area}/{Entity}.cs
|
|
118
|
+
|
|
119
|
+
public class {Entity} : BaseEntity, IAuditableEntity
|
|
120
|
+
{
|
|
121
|
+
// Proprietes
|
|
122
|
+
public string Name { get; private set; }
|
|
123
|
+
public string? Description { get; private set; }
|
|
124
|
+
public {Entity}Status Status { get; private set; }
|
|
125
|
+
|
|
126
|
+
// Relations
|
|
127
|
+
public Guid CreatedById { get; private set; }
|
|
128
|
+
public User CreatedBy { get; private set; } = null!;
|
|
129
|
+
|
|
130
|
+
// Audit
|
|
131
|
+
public string? CreatedByName { get; set; }
|
|
132
|
+
public string? UpdatedByName { get; set; }
|
|
133
|
+
|
|
134
|
+
// Private constructor for EF Core
|
|
135
|
+
private {Entity}() { }
|
|
136
|
+
|
|
137
|
+
// Factory Method
|
|
138
|
+
public static {Entity} Create(
|
|
139
|
+
string name,
|
|
140
|
+
string? description,
|
|
141
|
+
Guid createdById)
|
|
142
|
+
{
|
|
143
|
+
if (string.IsNullOrWhiteSpace(name))
|
|
144
|
+
throw new DomainException("{Entity} name is required");
|
|
145
|
+
|
|
146
|
+
return new {Entity}
|
|
147
|
+
{
|
|
148
|
+
Id = Guid.NewGuid(),
|
|
149
|
+
Name = name,
|
|
150
|
+
Description = description,
|
|
151
|
+
Status = {Entity}Status.Active,
|
|
152
|
+
CreatedById = createdById,
|
|
153
|
+
CreatedAt = DateTime.UtcNow
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Behaviors
|
|
158
|
+
public void Update(string name, string? description)
|
|
159
|
+
{
|
|
160
|
+
Name = name ?? throw new DomainException("Name is required");
|
|
161
|
+
Description = description;
|
|
162
|
+
UpdatedAt = DateTime.UtcNow;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public void Activate() => Status = {Entity}Status.Active;
|
|
166
|
+
public void Deactivate() => Status = {Entity}Status.Inactive;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 2. ENUM
|
|
170
|
+
// src/SmartStack.Domain/{Area}/Enums/{Entity}Status.cs
|
|
171
|
+
|
|
172
|
+
public enum {Entity}Status
|
|
173
|
+
{
|
|
174
|
+
Active = 0,
|
|
175
|
+
Inactive = 1,
|
|
176
|
+
Archived = 2
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### PHASE 3: GENERATION APPLICATION (10 min)
|
|
181
|
+
|
|
182
|
+
```csharp
|
|
183
|
+
// 1. INTERFACE SERVICE
|
|
184
|
+
// src/SmartStack.Application/Common/Interfaces/I{Entity}Service.cs
|
|
185
|
+
|
|
186
|
+
public interface I{Entity}Service
|
|
187
|
+
{
|
|
188
|
+
Task<PagedResult<{Entity}Dto>> GetAllAsync(
|
|
189
|
+
int page, int pageSize, string? search, CancellationToken ct);
|
|
190
|
+
Task<{Entity}Dto?> GetByIdAsync(Guid id, CancellationToken ct);
|
|
191
|
+
Task<{Entity}Dto> CreateAsync(Create{Entity}Request request, CancellationToken ct);
|
|
192
|
+
Task<{Entity}Dto> UpdateAsync(Guid id, Update{Entity}Request request, CancellationToken ct);
|
|
193
|
+
Task DeleteAsync(Guid id, CancellationToken ct);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 2. DTOs
|
|
197
|
+
// src/SmartStack.Application/{Area}/DTOs/{Entity}Dto.cs
|
|
198
|
+
|
|
199
|
+
public record {Entity}Dto(
|
|
200
|
+
Guid Id,
|
|
201
|
+
string Name,
|
|
202
|
+
string? Description,
|
|
203
|
+
string Status,
|
|
204
|
+
DateTime CreatedAt,
|
|
205
|
+
string? CreatedByName);
|
|
206
|
+
|
|
207
|
+
public record Create{Entity}Request(
|
|
208
|
+
string Name,
|
|
209
|
+
string? Description);
|
|
210
|
+
|
|
211
|
+
public record Update{Entity}Request(
|
|
212
|
+
string Name,
|
|
213
|
+
string? Description);
|
|
214
|
+
|
|
215
|
+
// 3. PERMISSIONS
|
|
216
|
+
// src/SmartStack.Application/Common/Authorization/Permissions.cs
|
|
217
|
+
|
|
218
|
+
public static class {Area}
|
|
219
|
+
{
|
|
220
|
+
public static class {Module}
|
|
221
|
+
{
|
|
222
|
+
public const string View = "{context}.{area}.{module}.read";
|
|
223
|
+
public const string Create = "{context}.{area}.{module}.create";
|
|
224
|
+
public const string Update = "{context}.{area}.{module}.update";
|
|
225
|
+
public const string Delete = "{context}.{area}.{module}.delete";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### PHASE 4: GENERATION INFRASTRUCTURE (15 min)
|
|
231
|
+
|
|
232
|
+
```csharp
|
|
233
|
+
// 1. CONFIGURATION EF CORE
|
|
234
|
+
// src/SmartStack.Infrastructure/Persistence/Configurations/{Area}/{Entity}Configuration.cs
|
|
235
|
+
|
|
236
|
+
public class {Entity}Configuration : IEntityTypeConfiguration<{Entity}>
|
|
237
|
+
{
|
|
238
|
+
public void Configure(EntityTypeBuilder<{Entity}> builder)
|
|
239
|
+
{
|
|
240
|
+
builder.ToTable("{Entity}s", "{schema}");
|
|
241
|
+
|
|
242
|
+
builder.HasKey(x => x.Id);
|
|
243
|
+
|
|
244
|
+
builder.Property(x => x.Name)
|
|
245
|
+
.HasMaxLength(200)
|
|
246
|
+
.IsRequired();
|
|
247
|
+
|
|
248
|
+
builder.Property(x => x.Description)
|
|
249
|
+
.HasMaxLength(2000);
|
|
250
|
+
|
|
251
|
+
builder.HasIndex(x => x.Name);
|
|
252
|
+
builder.HasIndex(x => x.Status);
|
|
253
|
+
builder.HasIndex(x => x.CreatedById);
|
|
254
|
+
|
|
255
|
+
// Navigation
|
|
256
|
+
builder.HasOne(x => x.CreatedBy)
|
|
257
|
+
.WithMany()
|
|
258
|
+
.HasForeignKey(x => x.CreatedById)
|
|
259
|
+
.OnDelete(DeleteBehavior.Restrict);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 2. SERVICE IMPLEMENTATION
|
|
264
|
+
// src/SmartStack.Infrastructure/Services/{Area}/{Entity}Service.cs
|
|
265
|
+
|
|
266
|
+
public class {Entity}Service : I{Entity}Service
|
|
267
|
+
{
|
|
268
|
+
private readonly IApplicationDbContext _context;
|
|
269
|
+
private readonly ICurrentUserService _currentUser;
|
|
270
|
+
private readonly INotificationService _notificationService;
|
|
271
|
+
private readonly ILogger<{Entity}Service> _logger;
|
|
272
|
+
|
|
273
|
+
public {Entity}Service(
|
|
274
|
+
IApplicationDbContext context,
|
|
275
|
+
ICurrentUserService currentUser,
|
|
276
|
+
INotificationService notificationService,
|
|
277
|
+
ILogger<{Entity}Service> logger)
|
|
278
|
+
{
|
|
279
|
+
_context = context;
|
|
280
|
+
_currentUser = currentUser;
|
|
281
|
+
_notificationService = notificationService;
|
|
282
|
+
_logger = logger;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public async Task<{Entity}Dto> CreateAsync(
|
|
286
|
+
Create{Entity}Request request,
|
|
287
|
+
CancellationToken ct)
|
|
288
|
+
{
|
|
289
|
+
var entity = {Entity}.Create(
|
|
290
|
+
request.Name,
|
|
291
|
+
request.Description,
|
|
292
|
+
_currentUser.Id);
|
|
293
|
+
|
|
294
|
+
entity.CreatedByName = _currentUser.DisplayName;
|
|
295
|
+
|
|
296
|
+
_context.{Entity}s.Add(entity);
|
|
297
|
+
await _context.SaveChangesAsync(ct);
|
|
298
|
+
|
|
299
|
+
// NOTIFICATION
|
|
300
|
+
await _notificationService.SendNotificationAsync(
|
|
301
|
+
_currentUser.Id,
|
|
302
|
+
NotificationType.{Entity}Created,
|
|
303
|
+
"{Entity} creee",
|
|
304
|
+
$"{entity.Name} a ete cree avec succes",
|
|
305
|
+
relatedEntityType: nameof({Entity}),
|
|
306
|
+
relatedEntityId: entity.Id,
|
|
307
|
+
actionUrl: $"/{area}/{module}/{entity.Id}",
|
|
308
|
+
cancellationToken: ct);
|
|
309
|
+
|
|
310
|
+
_logger.LogInformation(
|
|
311
|
+
"User {UserId} created {EntityType} {EntityId}: {EntityName}",
|
|
312
|
+
_currentUser.Id, nameof({Entity}), entity.Id, entity.Name);
|
|
313
|
+
|
|
314
|
+
return MapToDto(entity);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ... autres methodes CRUD avec notifications
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 3. DEPENDENCY INJECTION
|
|
321
|
+
// Ajouter dans DependencyInjection.cs:
|
|
322
|
+
services.AddScoped<I{Entity}Service, {Entity}Service>();
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### PHASE 5: GENERATION API (10 min)
|
|
326
|
+
|
|
327
|
+
```csharp
|
|
328
|
+
// src/SmartStack.Api/Controllers/{Area}/{Entity}Controller.cs
|
|
329
|
+
|
|
330
|
+
[ApiController]
|
|
331
|
+
[Route("api/{area}/{module}")]
|
|
332
|
+
[Authorize]
|
|
333
|
+
public class {Entity}Controller : ControllerBase
|
|
334
|
+
{
|
|
335
|
+
private readonly I{Entity}Service _service;
|
|
336
|
+
private readonly ILogger<{Entity}Controller> _logger;
|
|
337
|
+
|
|
338
|
+
public {Entity}Controller(
|
|
339
|
+
I{Entity}Service service,
|
|
340
|
+
ILogger<{Entity}Controller> logger)
|
|
341
|
+
{
|
|
342
|
+
_service = service;
|
|
343
|
+
_logger = logger;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
[HttpGet]
|
|
347
|
+
[RequirePermission(Permissions.{Area}.{Module}.View)]
|
|
348
|
+
[ProducesResponseType(typeof(PagedResult<{Entity}Dto>), StatusCodes.Status200OK)]
|
|
349
|
+
public async Task<ActionResult<PagedResult<{Entity}Dto>>> GetAll(
|
|
350
|
+
[FromQuery] int page = 1,
|
|
351
|
+
[FromQuery] int pageSize = 20,
|
|
352
|
+
[FromQuery] string? search = null,
|
|
353
|
+
CancellationToken ct = default)
|
|
354
|
+
{
|
|
355
|
+
var result = await _service.GetAllAsync(page, pageSize, search, ct);
|
|
356
|
+
return Ok(result);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
[HttpGet("{id:guid}")]
|
|
360
|
+
[RequirePermission(Permissions.{Area}.{Module}.View)]
|
|
361
|
+
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status200OK)]
|
|
362
|
+
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
363
|
+
public async Task<ActionResult<{Entity}Dto>> GetById(Guid id, CancellationToken ct)
|
|
364
|
+
{
|
|
365
|
+
var result = await _service.GetByIdAsync(id, ct);
|
|
366
|
+
return result is null ? NotFound() : Ok(result);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
[HttpPost]
|
|
370
|
+
[RequirePermission(Permissions.{Area}.{Module}.Create)]
|
|
371
|
+
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
|
|
372
|
+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
373
|
+
public async Task<ActionResult<{Entity}Dto>> Create(
|
|
374
|
+
[FromBody] Create{Entity}Request request,
|
|
375
|
+
CancellationToken ct)
|
|
376
|
+
{
|
|
377
|
+
var result = await _service.CreateAsync(request, ct);
|
|
378
|
+
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
[HttpPut("{id:guid}")]
|
|
382
|
+
[RequirePermission(Permissions.{Area}.{Module}.Update)]
|
|
383
|
+
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status200OK)]
|
|
384
|
+
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
385
|
+
public async Task<ActionResult<{Entity}Dto>> Update(
|
|
386
|
+
Guid id,
|
|
387
|
+
[FromBody] Update{Entity}Request request,
|
|
388
|
+
CancellationToken ct)
|
|
389
|
+
{
|
|
390
|
+
var result = await _service.UpdateAsync(id, request, ct);
|
|
391
|
+
return Ok(result);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
[HttpDelete("{id:guid}")]
|
|
395
|
+
[RequirePermission(Permissions.{Area}.{Module}.Delete)]
|
|
396
|
+
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
397
|
+
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
398
|
+
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
|
399
|
+
{
|
|
400
|
+
await _service.DeleteAsync(id, ct);
|
|
401
|
+
return NoContent();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### PHASE 6: GENERATION FRONTEND (20 min)
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// 1. API SERVICE
|
|
410
|
+
// web/src/services/api/{module}Api.ts
|
|
411
|
+
|
|
412
|
+
import { apiClient } from './apiClient';
|
|
413
|
+
import type { PagedResult, {Entity}Dto, Create{Entity}Request, Update{Entity}Request } from '@/types';
|
|
414
|
+
|
|
415
|
+
export const {module}Api = {
|
|
416
|
+
getAll: (page = 1, pageSize = 20, search?: string) =>
|
|
417
|
+
apiClient.get<PagedResult<{Entity}Dto>>('/{area}/{module}', {
|
|
418
|
+
params: { page, pageSize, search }
|
|
419
|
+
}),
|
|
420
|
+
|
|
421
|
+
getById: (id: string) =>
|
|
422
|
+
apiClient.get<{Entity}Dto>(`/{area}/{module}/${id}`),
|
|
423
|
+
|
|
424
|
+
create: (data: Create{Entity}Request) =>
|
|
425
|
+
apiClient.post<{Entity}Dto>('/{area}/{module}', data),
|
|
426
|
+
|
|
427
|
+
update: (id: string, data: Update{Entity}Request) =>
|
|
428
|
+
apiClient.put<{Entity}Dto>(`/{area}/{module}/${id}`, data),
|
|
429
|
+
|
|
430
|
+
delete: (id: string) =>
|
|
431
|
+
apiClient.delete(`/{area}/{module}/${id}`),
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// 2. HOOK AVEC SIGNALR
|
|
435
|
+
// web/src/hooks/use{Module}.ts
|
|
436
|
+
|
|
437
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
438
|
+
import { {module}Api } from '@/services/api/{module}Api';
|
|
439
|
+
import { useSignalR } from '@/hooks/useSignalR';
|
|
440
|
+
import { toast } from 'sonner';
|
|
441
|
+
|
|
442
|
+
export function use{Module}() {
|
|
443
|
+
const queryClient = useQueryClient();
|
|
444
|
+
|
|
445
|
+
// Real-time updates
|
|
446
|
+
useSignalR({
|
|
447
|
+
onNotification: (notification) => {
|
|
448
|
+
if (notification.relatedEntityType === '{Entity}') {
|
|
449
|
+
queryClient.invalidateQueries(['{module}']);
|
|
450
|
+
toast.info(notification.title, {
|
|
451
|
+
description: notification.message,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const {module}Query = useQuery({
|
|
458
|
+
queryKey: ['{module}'],
|
|
459
|
+
queryFn: () => {module}Api.getAll(),
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const createMutation = useMutation({
|
|
463
|
+
mutationFn: {module}Api.create,
|
|
464
|
+
onSuccess: () => {
|
|
465
|
+
queryClient.invalidateQueries(['{module}']);
|
|
466
|
+
toast.success('{Entity} creee');
|
|
467
|
+
},
|
|
468
|
+
onError: (error) => {
|
|
469
|
+
toast.error('Erreur lors de la creation');
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const updateMutation = useMutation({
|
|
474
|
+
mutationFn: ({ id, data }: { id: string; data: Update{Entity}Request }) =>
|
|
475
|
+
{module}Api.update(id, data),
|
|
476
|
+
onSuccess: () => {
|
|
477
|
+
queryClient.invalidateQueries(['{module}']);
|
|
478
|
+
toast.success('{Entity} mise a jour');
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const deleteMutation = useMutation({
|
|
483
|
+
mutationFn: {module}Api.delete,
|
|
484
|
+
onSuccess: () => {
|
|
485
|
+
queryClient.invalidateQueries(['{module}']);
|
|
486
|
+
toast.success('{Entity} supprimee');
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
{module}: {module}Query.data?.items ?? [],
|
|
492
|
+
isLoading: {module}Query.isLoading,
|
|
493
|
+
error: {module}Query.error,
|
|
494
|
+
create: createMutation.mutate,
|
|
495
|
+
update: updateMutation.mutate,
|
|
496
|
+
delete: deleteMutation.mutate,
|
|
497
|
+
isCreating: createMutation.isPending,
|
|
498
|
+
isUpdating: updateMutation.isPending,
|
|
499
|
+
isDeleting: deleteMutation.isPending,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// 3. PAGE PRINCIPALE
|
|
504
|
+
// web/src/pages/{area}/{module}/{Module}Page.tsx
|
|
505
|
+
|
|
506
|
+
import { useState } from 'react';
|
|
507
|
+
import { Plus, Search } from 'lucide-react';
|
|
508
|
+
import { use{Module} } from '@/hooks/use{Module}';
|
|
509
|
+
import { EntityCard } from '@/components/ui/EntityCard';
|
|
510
|
+
import { {Module}Form } from './components/{Module}Form';
|
|
511
|
+
import { useTranslation } from 'react-i18next';
|
|
512
|
+
|
|
513
|
+
export function {Module}Page() {
|
|
514
|
+
const { t } = useTranslation('{module}');
|
|
515
|
+
const { {module}, isLoading, create, delete: remove } = use{Module}();
|
|
516
|
+
const [showForm, setShowForm] = useState(false);
|
|
517
|
+
const [search, setSearch] = useState('');
|
|
518
|
+
|
|
519
|
+
const filtered = {module}.filter(item =>
|
|
520
|
+
item.name.toLowerCase().includes(search.toLowerCase())
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
return (
|
|
524
|
+
<div className="space-y-6">
|
|
525
|
+
{/* Header */}
|
|
526
|
+
<div className="flex items-center justify-between">
|
|
527
|
+
<div>
|
|
528
|
+
<h1 className="text-2xl font-bold">{t('title')}</h1>
|
|
529
|
+
<p className="text-[var(--text-secondary)]">{t('subtitle')}</p>
|
|
530
|
+
</div>
|
|
531
|
+
<button
|
|
532
|
+
onClick={() => setShowForm(true)}
|
|
533
|
+
className="inline-flex items-center gap-2 px-4 py-2 bg-[var(--color-accent-600)] text-white rounded-lg"
|
|
534
|
+
>
|
|
535
|
+
<Plus className="w-4 h-4" />
|
|
536
|
+
{t('create')}
|
|
537
|
+
</button>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
{/* Search */}
|
|
541
|
+
<div className="relative">
|
|
542
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--text-tertiary)]" />
|
|
543
|
+
<input
|
|
544
|
+
type="text"
|
|
545
|
+
placeholder={t('search')}
|
|
546
|
+
value={search}
|
|
547
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
548
|
+
className="w-full pl-10 pr-4 py-2 border rounded-lg"
|
|
549
|
+
/>
|
|
550
|
+
</div>
|
|
551
|
+
|
|
552
|
+
{/* Grid */}
|
|
553
|
+
{isLoading ? (
|
|
554
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
555
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
556
|
+
<div key={i} className="h-48 bg-[var(--bg-secondary)] animate-pulse rounded-lg" />
|
|
557
|
+
))}
|
|
558
|
+
</div>
|
|
559
|
+
) : filtered.length === 0 ? (
|
|
560
|
+
<div className="text-center py-12">
|
|
561
|
+
<p className="text-[var(--text-secondary)]">{t('empty')}</p>
|
|
562
|
+
</div>
|
|
563
|
+
) : (
|
|
564
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
565
|
+
{filtered.map((item) => (
|
|
566
|
+
<EntityCard
|
|
567
|
+
key={item.id}
|
|
568
|
+
avatar={{ letter: item.name[0], color: 'var(--color-accent-500)' }}
|
|
569
|
+
title={item.name}
|
|
570
|
+
subtitle={item.status}
|
|
571
|
+
description={item.description}
|
|
572
|
+
actions={[
|
|
573
|
+
{ label: t('view'), onClick: () => navigate(`/${area}/{module}/${item.id}`), variant: 'primary' },
|
|
574
|
+
{ label: t('delete'), onClick: () => remove(item.id), variant: 'ghost' },
|
|
575
|
+
]}
|
|
576
|
+
/>
|
|
577
|
+
))}
|
|
578
|
+
</div>
|
|
579
|
+
)}
|
|
580
|
+
|
|
581
|
+
{/* Form Modal */}
|
|
582
|
+
{showForm && (
|
|
583
|
+
<{Module}Form
|
|
584
|
+
onSubmit={(data) => {
|
|
585
|
+
create(data);
|
|
586
|
+
setShowForm(false);
|
|
587
|
+
}}
|
|
588
|
+
onClose={() => setShowForm(false)}
|
|
589
|
+
/>
|
|
590
|
+
)}
|
|
591
|
+
</div>
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 4. i18n
|
|
596
|
+
// web/src/i18n/locales/fr/{module}.json
|
|
597
|
+
|
|
598
|
+
{
|
|
599
|
+
"title": "{Module}",
|
|
600
|
+
"subtitle": "Gerez vos {module}s",
|
|
601
|
+
"create": "Creer",
|
|
602
|
+
"search": "Rechercher...",
|
|
603
|
+
"empty": "Aucun {module} trouve",
|
|
604
|
+
"view": "Voir",
|
|
605
|
+
"edit": "Modifier",
|
|
606
|
+
"delete": "Supprimer",
|
|
607
|
+
"form": {
|
|
608
|
+
"name": "Nom",
|
|
609
|
+
"namePlaceholder": "Entrez le nom",
|
|
610
|
+
"description": "Description",
|
|
611
|
+
"descriptionPlaceholder": "Entrez une description",
|
|
612
|
+
"submit": "Enregistrer",
|
|
613
|
+
"cancel": "Annuler"
|
|
614
|
+
},
|
|
615
|
+
"notifications": {
|
|
616
|
+
"created": "{Module} creee avec succes",
|
|
617
|
+
"updated": "{Module} mise a jour",
|
|
618
|
+
"deleted": "{Module} supprimee"
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// 5. ROUTES
|
|
623
|
+
// App.tsx - Ajouter:
|
|
624
|
+
<Route path="{area}">
|
|
625
|
+
<Route index element={<Navigate to="{module}" replace />} />
|
|
626
|
+
<Route path="{module}">
|
|
627
|
+
<Route index element={<{Module}Page />} />
|
|
628
|
+
<Route path=":id" element={<{Module}DetailPage />} />
|
|
629
|
+
</Route>
|
|
630
|
+
</Route>
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### PHASE 7: INTEGRATIONS (15 min)
|
|
634
|
+
|
|
635
|
+
#### 7.1 Notifications
|
|
636
|
+
|
|
637
|
+
```csharp
|
|
638
|
+
// Dans le service, ajouter les notifications appropriees:
|
|
639
|
+
|
|
640
|
+
// Creation
|
|
641
|
+
await _notificationService.SendNotificationAsync(
|
|
642
|
+
_currentUser.Id,
|
|
643
|
+
NotificationType.{Entity}Created,
|
|
644
|
+
t["notifications.created.title"],
|
|
645
|
+
t["notifications.created.message", entity.Name],
|
|
646
|
+
relatedEntityType: nameof({Entity}),
|
|
647
|
+
relatedEntityId: entity.Id,
|
|
648
|
+
actionUrl: $"/{area}/{module}/{entity.Id}");
|
|
649
|
+
|
|
650
|
+
// Si assignation
|
|
651
|
+
if (entity.AssignedToId.HasValue)
|
|
652
|
+
{
|
|
653
|
+
await _notificationService.SendNotificationAsync(
|
|
654
|
+
entity.AssignedToId.Value,
|
|
655
|
+
NotificationType.{Entity}Assigned,
|
|
656
|
+
"{Entity} assignee",
|
|
657
|
+
$"La {entity.Name} vous a ete assignee",
|
|
658
|
+
relatedEntityType: nameof({Entity}),
|
|
659
|
+
relatedEntityId: entity.Id,
|
|
660
|
+
actionUrl: $"/{area}/{module}/{entity.Id}");
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
#### 7.2 Workflow Email
|
|
665
|
+
|
|
666
|
+
```csharp
|
|
667
|
+
// 1. Creer le trigger
|
|
668
|
+
// Dans WorkflowTriggerConfiguration.cs:
|
|
669
|
+
new
|
|
670
|
+
{
|
|
671
|
+
Id = Guid.Parse("NEW-TRIGGER-GUID"),
|
|
672
|
+
Code = "{entity}.created",
|
|
673
|
+
Name = "{Entity} Created",
|
|
674
|
+
AvailableVariablesJson = "[{\"Name\":\"entityId\"},{\"Name\":\"entityName\"},{\"Name\":\"creatorEmail\"}]",
|
|
675
|
+
IsActive = true,
|
|
676
|
+
CreatedAt = seedDate
|
|
677
|
+
},
|
|
678
|
+
|
|
679
|
+
// 2. Creer le workflow
|
|
680
|
+
// Dans WorkflowConfiguration.cs:
|
|
681
|
+
new
|
|
682
|
+
{
|
|
683
|
+
Id = Guid.Parse("NEW-WORKFLOW-GUID"),
|
|
684
|
+
Code = "{entity}-created-notification",
|
|
685
|
+
Name = "{Entity} Created Notification",
|
|
686
|
+
TriggerId = Guid.Parse("TRIGGER-GUID"),
|
|
687
|
+
IsActive = true,
|
|
688
|
+
IsSystem = true,
|
|
689
|
+
Priority = 10,
|
|
690
|
+
CreatedAt = seedDate
|
|
691
|
+
},
|
|
692
|
+
|
|
693
|
+
// 3. Declencher dans le service
|
|
694
|
+
await _workflowService.TriggerAsync(
|
|
695
|
+
"{entity}.created",
|
|
696
|
+
new Dictionary<string, object>
|
|
697
|
+
{
|
|
698
|
+
["entityId"] = entity.Id,
|
|
699
|
+
["entityName"] = entity.Name,
|
|
700
|
+
["creatorEmail"] = _currentUser.Email
|
|
701
|
+
});
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
#### 7.3 AI Integration
|
|
705
|
+
|
|
706
|
+
```csharp
|
|
707
|
+
// Si analyse IA requise:
|
|
708
|
+
public async Task<{Entity}AnalysisResult?> AnalyzeAsync(Guid entityId, CancellationToken ct)
|
|
709
|
+
{
|
|
710
|
+
var entity = await _context.{Entity}s.FindAsync(entityId);
|
|
711
|
+
if (entity == null) return null;
|
|
712
|
+
|
|
713
|
+
var result = await _aiCompletionService
|
|
714
|
+
.ExecutePromptByCodeWithValidationAsync<{Entity}AnalysisResult>(
|
|
715
|
+
"{entity}-analyzer",
|
|
716
|
+
new Dictionary<string, object>
|
|
717
|
+
{
|
|
718
|
+
["entityName"] = entity.Name,
|
|
719
|
+
["entityDescription"] = entity.Description ?? ""
|
|
720
|
+
},
|
|
721
|
+
cancellationToken: ct);
|
|
722
|
+
|
|
723
|
+
if (result.Success && result.IsValid)
|
|
724
|
+
{
|
|
725
|
+
_logger.LogInformation(
|
|
726
|
+
"{Entity} {EntityId} analyzed: {Category}",
|
|
727
|
+
entityId, result.Data.Category);
|
|
728
|
+
return result.Data;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## CHECKLIST COMPLETE
|
|
738
|
+
|
|
739
|
+
### Domain Layer
|
|
740
|
+
```
|
|
741
|
+
□ Entite {Entity}.cs creee
|
|
742
|
+
□ Factory Method Create() implemente
|
|
743
|
+
□ Behaviors (Update, Activate, etc.)
|
|
744
|
+
□ Enum {Entity}Status.cs
|
|
745
|
+
□ IAuditableEntity implemente
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
### Application Layer
|
|
749
|
+
```
|
|
750
|
+
□ Interface I{Entity}Service.cs
|
|
751
|
+
□ DTOs: {Entity}Dto, Create/Update Requests
|
|
752
|
+
□ Permissions ajoutees dans Permissions.cs
|
|
753
|
+
□ Permissions ajoutees dans PermissionConfiguration.cs
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### Infrastructure Layer
|
|
757
|
+
```
|
|
758
|
+
□ {Entity}Configuration.cs (EF Core)
|
|
759
|
+
□ {Entity}Service.cs implementation
|
|
760
|
+
□ Injection DI dans DependencyInjection.cs
|
|
761
|
+
□ DbSet<{Entity}> dans ApplicationDbContext
|
|
762
|
+
□ Migration EF Core creee
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### API Layer
|
|
766
|
+
```
|
|
767
|
+
□ {Entity}Controller.cs
|
|
768
|
+
□ [Authorize] et [RequirePermission]
|
|
769
|
+
□ [ProducesResponseType] documentes
|
|
770
|
+
□ Logging dans les actions
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### Web Layer
|
|
774
|
+
```
|
|
775
|
+
□ {module}Api.ts (service API)
|
|
776
|
+
□ use{Module}.ts (hook avec SignalR)
|
|
777
|
+
□ {Module}Page.tsx (page principale)
|
|
778
|
+
□ {Module}Form.tsx (formulaire)
|
|
779
|
+
□ {Module}DetailPage.tsx (detail)
|
|
780
|
+
□ i18n: fr/{module}.json + en/{module}.json
|
|
781
|
+
□ Routes dans App.tsx
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### Integrations
|
|
785
|
+
```
|
|
786
|
+
□ Notifications:
|
|
787
|
+
□ NotificationType ajoute
|
|
788
|
+
□ SendNotificationAsync dans le service
|
|
789
|
+
□ useSignalR dans le hook
|
|
790
|
+
□ Workflows (si emails requis):
|
|
791
|
+
□ Trigger cree
|
|
792
|
+
□ Workflow cree
|
|
793
|
+
□ TriggerAsync dans le service
|
|
794
|
+
□ AI (si analyse requise):
|
|
795
|
+
□ Prompt cree
|
|
796
|
+
□ OutputSchema cree
|
|
797
|
+
□ ExecutePromptAsync dans le service
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
### Validation
|
|
801
|
+
```
|
|
802
|
+
□ dotnet build OK
|
|
803
|
+
□ npm run build OK
|
|
804
|
+
□ npm run lint OK
|
|
805
|
+
□ Tests unitaires
|
|
806
|
+
□ Test manuel E2E
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## SKILLS ASSOCIES
|
|
812
|
+
|
|
813
|
+
Ce skill orchestre les skills suivants :
|
|
814
|
+
|
|
815
|
+
| Skill | Phase | Action |
|
|
816
|
+
|-------|-------|--------|
|
|
817
|
+
| `/application` | 2-6 | Creation module full-stack |
|
|
818
|
+
| `/controller` | 5 | Generation controller API |
|
|
819
|
+
| `/notification` | 7.1 | Integration notifications |
|
|
820
|
+
| `/workflow` | 7.2 | Integration workflows/emails |
|
|
821
|
+
| `/ai-prompt` | 7.3 | Integration IA |
|
|
822
|
+
| `/ui-components` | 6 | Composants UI (EntityCard) |
|
|
823
|
+
| `/efcore:migration` | 4 | Migration EF Core |
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## REGLES ABSOLUES
|
|
828
|
+
|
|
829
|
+
1. **TOUJOURS** suivre l'architecture en couches
|
|
830
|
+
2. **TOUJOURS** utiliser Factory Methods pour les entites
|
|
831
|
+
3. **TOUJOURS** implementer IAuditableEntity
|
|
832
|
+
4. **TOUJOURS** ajouter les permissions dans les 2 fichiers
|
|
833
|
+
5. **TOUJOURS** utiliser les hooks avec SignalR
|
|
834
|
+
6. **TOUJOURS** internationaliser (4 langues: fr, en, it, de)
|
|
835
|
+
7. **TOUJOURS** logger les operations importantes
|
|
836
|
+
8. **TOUJOURS** documenter l'API avec ProducesResponseType
|
|
837
|
+
9. **JAMAIS** d'acces DB direct depuis le frontend
|
|
838
|
+
10. **JAMAIS** de permissions hardcodees en strings
|