stackforge-cli 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl

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.
@@ -1,544 +1,544 @@
1
- from pathlib import Path
2
- from stackforge import ui
3
-
4
-
5
- def generate_application_layer(base: Path, project: str):
6
- folders = [
7
- "Application/Common/Behaviors",
8
- "Application/Common/Constants",
9
- "Application/Common/Exceptions",
10
- "Application/Common/Models/Serialization",
11
- "Application/Helpers",
12
- "Application/Interfaces",
13
- "Application/Mappings",
14
- "Application/Services",
15
- ]
16
- for folder in folders:
17
- (base / folder).mkdir(parents=True, exist_ok=True)
18
-
19
- _gen_validation_behavior(base, project)
20
- _gen_error_messages(base, project)
21
- _gen_app_exceptions(base, project)
22
- _gen_not_found_exception(base, project)
23
- _gen_api_response(base, project)
24
- _gen_result(base, project)
25
- _gen_paging_params(base, project)
26
- _gen_paginated_result(base, project)
27
- _gen_paged_list(base, project)
28
- _gen_paged_response(base, project)
29
- _gen_iso_datetime(base, project)
30
-
31
- ui.file_created("Application/ structure and files")
32
-
33
-
34
- def generate_domain_layer(base: Path, project: str):
35
- folders = [
36
- "Domain/Common",
37
- "Domain/Entities/Identity",
38
- "Domain/Enums",
39
- "Domain/Utils",
40
- ]
41
- for folder in folders:
42
- (base / folder).mkdir(parents=True, exist_ok=True)
43
-
44
- _gen_base_entity(base, project)
45
- _gen_enum_member_extensions(base, project)
46
- _gen_role(base, project)
47
- _gen_user(base, project)
48
- _gen_user_role(base, project)
49
- _gen_global_usings(base, project)
50
-
51
- ui.file_created("Domain/ structure and files")
52
-
53
-
54
- def generate_infrastructure_layer(base: Path, project: str):
55
- folders = [
56
- "Infrastructure/BackgroundServices",
57
- "Infrastructure/Configurations",
58
- "Infrastructure/Options",
59
- "Infrastructure/Services",
60
- ]
61
- for folder in folders:
62
- (base / folder).mkdir(parents=True, exist_ok=True)
63
-
64
- ui.file_created("Infrastructure/ structure and files")
65
-
66
-
67
- def generate_persistence_layer(base: Path, project: str):
68
- folders = [
69
- "Persistence/Configurations",
70
- "Persistence/Extensions",
71
- "Persistence/Migrations",
72
- "Persistence/Seeders",
73
- ]
74
- for folder in folders:
75
- (base / folder).mkdir(parents=True, exist_ok=True)
76
-
77
- _gen_data_context(base, project)
78
- _gen_role_seeder(base, project)
79
- _gen_user_seeder(base, project)
80
- _gen_user_role_configuration(base, project)
81
-
82
- ui.file_created("Persistence/ structure and files")
83
-
84
-
85
- # ── Application files ──────────────────────────────────────────────────────────
86
-
87
- def _gen_validation_behavior(base: Path, project: str):
88
- content = f"""using FluentValidation;
89
- using MediatR;
90
-
91
- namespace {project}.Application.Common.Behaviors
92
- {{
93
- public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
94
- where TRequest : notnull
95
- {{
96
- private readonly IEnumerable<IValidator<TRequest>> _validators;
97
-
98
- public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
99
- {{
100
- _validators = validators;
101
- }}
102
-
103
- public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
104
- {{
105
- if (_validators.Any())
106
- {{
107
- var context = new ValidationContext<TRequest>(request);
108
- var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
109
- var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
110
- if (failures.Count != 0)
111
- throw new ValidationException(failures);
112
- }}
113
- return await next();
114
- }}
115
- }}
116
- }}
117
- """
118
- (base / "Application/Common/Behaviors/ValidationBehavior.cs").write_text(content)
119
-
120
-
121
- def _gen_error_messages(base: Path, project: str):
122
- content = f"""namespace {project}.Application.Common.Constants
123
- {{
124
- public static class ErrorMessages
125
- {{
126
- public const string NotFound = "The requested resource was not found.";
127
- public const string Unauthorized = "You are not authorized to perform this action.";
128
- public const string BadRequest = "The request is invalid.";
129
- public const string InternalServerError = "An unexpected error occurred.";
130
- }}
131
- }}
132
- """
133
- (base / "Application/Common/Constants/ErrorMessages.cs").write_text(content)
134
-
135
-
136
- def _gen_app_exceptions(base: Path, project: str):
137
- content = f"""namespace {project}.Application.Common.Exceptions
138
- {{
139
- public class AppException : Exception
140
- {{
141
- public int StatusCode {{ get; }}
142
-
143
- public AppException(string message, int statusCode = 400) : base(message)
144
- {{
145
- StatusCode = statusCode;
146
- }}
147
- }}
148
- }}
149
- """
150
- (base / "Application/Common/Exceptions/AppExceptions.cs").write_text(content)
151
-
152
-
153
- def _gen_not_found_exception(base: Path, project: str):
154
- content = f"""namespace {project}.Application.Common.Exceptions
155
- {{
156
- public class NotFoundException : Exception
157
- {{
158
- public NotFoundException(string name, object key)
159
- : base($"{{name}} ({{key}}) was not found.")
160
- {{
161
- }}
162
- }}
163
- }}
164
- """
165
- (base / "Application/Common/Exceptions/NotFoundException.cs").write_text(content)
166
-
167
-
168
- def _gen_api_response(base: Path, project: str):
169
- content = f"""namespace {project}.Application.Common.Models
170
- {{
171
- public class ApiResponse<T>
172
- {{
173
- public bool Success {{ get; set; }}
174
- public string? Message {{ get; set; }}
175
- public T? Data {{ get; set; }}
176
-
177
- public static ApiResponse<T> Ok(T data, string message = "Success") =>
178
- new() {{ Success = true, Message = message, Data = data }};
179
-
180
- public static ApiResponse<T> Fail(string message) =>
181
- new() {{ Success = false, Message = message }};
182
- }}
183
- }}
184
- """
185
- (base / "Application/Common/Models/ApiResponse.cs").write_text(content)
186
-
187
-
188
- def _gen_result(base: Path, project: str):
189
- content = f"""namespace {project}.Application.Common.Models
190
- {{
191
- public class Result
192
- {{
193
- public bool IsSuccess {{ get; }}
194
- public string? Error {{ get; }}
195
-
196
- protected Result(bool isSuccess, string? error)
197
- {{
198
- IsSuccess = isSuccess;
199
- Error = error;
200
- }}
201
-
202
- public static Result Success() => new(true, null);
203
- public static Result Failure(string error) => new(false, error);
204
- }}
205
-
206
- public class Result<T> : Result
207
- {{
208
- public T? Value {{ get; }}
209
-
210
- protected Result(bool isSuccess, T? value, string? error) : base(isSuccess, error)
211
- {{
212
- Value = value;
213
- }}
214
-
215
- public static Result<T> Success(T value) => new(true, value, null);
216
- public static new Result<T> Failure(string error) => new(false, default, error);
217
- }}
218
- }}
219
- """
220
- (base / "Application/Common/Models/Result.cs").write_text(content)
221
-
222
-
223
- def _gen_paging_params(base: Path, project: str):
224
- content = f"""namespace {project}.Application.Common.Models
225
- {{
226
- public class PagingParams
227
- {{
228
- public int PageNumber {{ get; set; }} = 1;
229
- public int PageSize {{ get; set; }} = 10;
230
- }}
231
- }}
232
- """
233
- (base / "Application/Common/Models/PagingParams.cs").write_text(content)
234
-
235
-
236
- def _gen_paginated_result(base: Path, project: str):
237
- content = f"""namespace {project}.Application.Common.Models
238
- {{
239
- public class PaginatedResult<T>
240
- {{
241
- public List<T> Items {{ get; set; }} = new();
242
- public int TotalCount {{ get; set; }}
243
- public int PageNumber {{ get; set; }}
244
- public int PageSize {{ get; set; }}
245
- public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
246
- public bool HasPreviousPage => PageNumber > 1;
247
- public bool HasNextPage => PageNumber < TotalPages;
248
- }}
249
- }}
250
- """
251
- (base / "Application/Common/Models/PaginatedResult.cs").write_text(content)
252
-
253
-
254
- def _gen_paged_list(base: Path, project: str):
255
- content = f"""using Microsoft.EntityFrameworkCore;
256
-
257
- namespace {project}.Application.Common.Models
258
- {{
259
- public class PagedList<T>
260
- {{
261
- public List<T> Items {{ get; }}
262
- public int TotalCount {{ get; }}
263
- public int PageNumber {{ get; }}
264
- public int PageSize {{ get; }}
265
- public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
266
- public bool HasPreviousPage => PageNumber > 1;
267
- public bool HasNextPage => PageNumber < TotalPages;
268
-
269
- private PagedList(List<T> items, int totalCount, int pageNumber, int pageSize)
270
- {{
271
- Items = items;
272
- TotalCount = totalCount;
273
- PageNumber = pageNumber;
274
- PageSize = pageSize;
275
- }}
276
-
277
- public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
278
- {{
279
- var totalCount = await source.CountAsync();
280
- var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
281
- return new PagedList<T>(items, totalCount, pageNumber, pageSize);
282
- }}
283
- }}
284
- }}
285
- """
286
- (base / "Application/Common/Models/PagedList.cs").write_text(content)
287
-
288
-
289
- def _gen_paged_response(base: Path, project: str):
290
- content = f"""namespace {project}.Application.Common.Models
291
- {{
292
- public class PagedResponse<T>
293
- {{
294
- public List<T> Items {{ get; set; }} = new();
295
- public int TotalCount {{ get; set; }}
296
- public int PageNumber {{ get; set; }}
297
- public int PageSize {{ get; set; }}
298
- public int TotalPages {{ get; set; }}
299
- public bool HasPreviousPage {{ get; set; }}
300
- public bool HasNextPage {{ get; set; }}
301
- }}
302
- }}
303
- """
304
- (base / "Application/Common/Models/PagedResponse.cs").write_text(content)
305
-
306
-
307
- def _gen_iso_datetime(base: Path, project: str):
308
- content = f"""using System.Text.Json;
309
- using System.Text.Json.Serialization;
310
-
311
- namespace {project}.Application.Common.Models.Serialization
312
- {{
313
- public class IsoDateTimeConverter : JsonConverter<DateTime>
314
- {{
315
- public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
316
- => DateTime.Parse(reader.GetString()!);
317
-
318
- public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
319
- => writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"));
320
- }}
321
- }}
322
- """
323
- (base / "Application/Common/Models/Serialization/IsoDateTime.cs").write_text(content)
324
-
325
-
326
- # ── Domain files ───────────────────────────────────────────────────────────────
327
-
328
- def _gen_base_entity(base: Path, project: str):
329
- content = f"""namespace {project}.Domain.Common
330
- {{
331
- public abstract class BaseEntity
332
- {{
333
- public Guid Id {{ get; set; }} = Guid.NewGuid();
334
- public DateTime CreatedAt {{ get; set; }} = DateTime.UtcNow;
335
- public DateTime UpdatedAt {{ get; set; }} = DateTime.UtcNow;
336
- }}
337
- }}
338
- """
339
- (base / "Domain/Common/BaseEntity.cs").write_text(content)
340
-
341
-
342
- def _gen_enum_member_extensions(base: Path, project: str):
343
- content = f"""using System.Runtime.Serialization;
344
- using System.Reflection;
345
-
346
- namespace {project}.Domain.Common
347
- {{
348
- public static class EnumMemberExtensions
349
- {{
350
- public static string ToEnumMemberValue<T>(this T value) where T : Enum
351
- {{
352
- return typeof(T)
353
- .GetField(value.ToString())
354
- ?.GetCustomAttribute<EnumMemberAttribute>()
355
- ?.Value ?? value.ToString();
356
- }}
357
- }}
358
- }}
359
- """
360
- (base / "Domain/Common/EnumMemberExtensions.cs").write_text(content)
361
-
362
-
363
- def _gen_role(base: Path, project: str):
364
- content = f"""using Microsoft.AspNetCore.Identity;
365
-
366
- namespace {project}.Domain.Entities.Identity
367
- {{
368
- public class Role : IdentityRole<Guid>
369
- {{
370
- public ICollection<UserRole> UserRoles {{ get; set; }} = new List<UserRole>();
371
- }}
372
- }}
373
- """
374
- (base / "Domain/Entities/Identity/Role.cs").write_text(content)
375
-
376
-
377
- def _gen_user(base: Path, project: str):
378
- content = f"""using Microsoft.AspNetCore.Identity;
379
-
380
- namespace {project}.Domain.Entities.Identity
381
- {{
382
- public class User : IdentityUser<Guid>
383
- {{
384
- public string FirstName {{ get; set; }} = string.Empty;
385
- public string LastName {{ get; set; }} = string.Empty;
386
- public DateTime CreatedAt {{ get; set; }} = DateTime.UtcNow;
387
- public DateTime UpdatedAt {{ get; set; }} = DateTime.UtcNow;
388
- public ICollection<UserRole> UserRoles {{ get; set; }} = new List<UserRole>();
389
- }}
390
- }}
391
- """
392
- (base / "Domain/Entities/Identity/User.cs").write_text(content)
393
-
394
-
395
- def _gen_user_role(base: Path, project: str):
396
- content = f"""using Microsoft.AspNetCore.Identity;
397
-
398
- namespace {project}.Domain.Entities.Identity
399
- {{
400
- public class UserRole : IdentityUserRole<Guid>
401
- {{
402
- public virtual User User {{ get; set; }} = null!;
403
- public virtual Role Role {{ get; set; }} = null!;
404
- }}
405
- }}
406
- """
407
- (base / "Domain/Entities/Identity/UserRole.cs").write_text(content)
408
-
409
-
410
- def _gen_global_usings(base: Path, project: str):
411
- content = f"""global using {project}.Domain.Common;
412
- global using {project}.Domain.Entities;
413
- """
414
- (base / "Domain/GlobalUsings.cs").write_text(content)
415
-
416
-
417
- # ── Persistence files ──────────────────────────────────────────────────────────
418
-
419
- def _gen_data_context(base: Path, project: str):
420
- identity_db_context = "IdentityDbContext<"
421
- content = f"""using {project}.Domain.Entities.Identity;
422
- using Humanizer;
423
- using Microsoft.AspNetCore.Identity;
424
- using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
425
- using Microsoft.EntityFrameworkCore;
426
- using System.Reflection;
427
-
428
- namespace {project}.Persistence
429
- {{
430
- public class DataContext : {identity_db_context}
431
- User,
432
- Role,
433
- Guid,
434
- IdentityUserClaim<Guid>,
435
- UserRole,
436
- IdentityUserLogin<Guid>,
437
- IdentityRoleClaim<Guid>,
438
- IdentityUserToken<Guid>>
439
- {{
440
- public DataContext(DbContextOptions<DataContext> options) : base(options)
441
- {{
442
- }}
443
-
444
- protected override void OnModelCreating(ModelBuilder builder)
445
- {{
446
- base.OnModelCreating(builder);
447
- builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
448
- foreach (var entityType in builder.Model.GetEntityTypes())
449
- {{
450
- var table = entityType.GetTableName();
451
- if (table!.StartsWith("AspNet"))
452
- entityType.SetTableName(table.Substring(6).Underscore());
453
- }}
454
- }}
455
- }}
456
- }}
457
- """
458
- (base / "Persistence/DataContext.cs").write_text(content)
459
-
460
-
461
- def _gen_role_seeder(base: Path, project: str):
462
- content = f"""using {project}.Domain.Entities.Identity;
463
- using Microsoft.AspNetCore.Identity;
464
-
465
- namespace {project}.Persistence.Seeders
466
- {{
467
- public static class RoleSeeder
468
- {{
469
- public static async Task SeedAsync(RoleManager<Role> roleManager)
470
- {{
471
- string[] roles = {{ "SuperAdmin", "Admin", "User" }};
472
-
473
- foreach (var role in roles)
474
- {{
475
- if (!await roleManager.RoleExistsAsync(role))
476
- await roleManager.CreateAsync(new Role {{ Name = role }});
477
- }}
478
- }}
479
- }}
480
- }}
481
- """
482
- (base / "Persistence/Seeders/RoleSeeder.cs").write_text(content)
483
-
484
-
485
- def _gen_user_seeder(base: Path, project: str):
486
- content = f"""using {project}.Domain.Entities.Identity;
487
- using Microsoft.AspNetCore.Identity;
488
-
489
- namespace {project}.Persistence.Seeders
490
- {{
491
- public static class UserSeeder
492
- {{
493
- public static async Task SeedAsync(UserManager<User> userManager)
494
- {{
495
- var superAdmin = await userManager.FindByEmailAsync("super_admin@{project.lower()}.com");
496
-
497
- if (superAdmin == null)
498
- {{
499
- var user = new User
500
- {{
501
- UserName = "super_admin@{project.lower()}.com",
502
- Email = "super_admin@{project.lower()}.com",
503
- FirstName = "Super",
504
- LastName = "Admin",
505
- EmailConfirmed = true,
506
- }};
507
-
508
- await userManager.CreateAsync(user, "Pa$$w0rd");
509
- await userManager.AddToRoleAsync(user, "SuperAdmin");
510
- }}
511
- }}
512
- }}
513
- }}
514
- """
515
- (base / "Persistence/Seeders/UserSeeder.cs").write_text(content)
516
-
517
-
518
- def _gen_user_role_configuration(base: Path, project: str):
519
- content = f"""using {project}.Domain.Entities.Identity;
520
- using Microsoft.EntityFrameworkCore;
521
- using Microsoft.EntityFrameworkCore.Metadata.Builders;
522
-
523
- namespace {project}.Persistence.Configurations
524
- {{
525
- public class UserRoleConfiguration : IEntityTypeConfiguration<UserRole>
526
- {{
527
- public void Configure(EntityTypeBuilder<UserRole> builder)
528
- {{
529
- builder.HasKey(ur => new {{ ur.UserId, ur.RoleId }});
530
-
531
- builder.HasOne(ur => ur.User)
532
- .WithMany(u => u.UserRoles)
533
- .HasForeignKey(ur => ur.UserId)
534
- .IsRequired();
535
-
536
- builder.HasOne(ur => ur.Role)
537
- .WithMany(r => r.UserRoles)
538
- .HasForeignKey(ur => ur.RoleId)
539
- .IsRequired();
540
- }}
541
- }}
542
- }}
543
- """
1
+ from pathlib import Path
2
+ from stackforge import ui
3
+
4
+
5
+ def generate_application_layer(base: Path, project: str):
6
+ folders = [
7
+ "Application/Common/Behaviors",
8
+ "Application/Common/Constants",
9
+ "Application/Common/Exceptions",
10
+ "Application/Common/Models/Serialization",
11
+ "Application/Helpers",
12
+ "Application/Interfaces",
13
+ "Application/Mappings",
14
+ "Application/Services",
15
+ ]
16
+ for folder in folders:
17
+ (base / folder).mkdir(parents=True, exist_ok=True)
18
+
19
+ _gen_validation_behavior(base, project)
20
+ _gen_error_messages(base, project)
21
+ _gen_app_exceptions(base, project)
22
+ _gen_not_found_exception(base, project)
23
+ _gen_api_response(base, project)
24
+ _gen_result(base, project)
25
+ _gen_paging_params(base, project)
26
+ _gen_paginated_result(base, project)
27
+ _gen_paged_list(base, project)
28
+ _gen_paged_response(base, project)
29
+ _gen_iso_datetime(base, project)
30
+
31
+ ui.file_created("Application/ structure and files")
32
+
33
+
34
+ def generate_domain_layer(base: Path, project: str):
35
+ folders = [
36
+ "Domain/Common",
37
+ "Domain/Entities/Identity",
38
+ "Domain/Enums",
39
+ "Domain/Utils",
40
+ ]
41
+ for folder in folders:
42
+ (base / folder).mkdir(parents=True, exist_ok=True)
43
+
44
+ _gen_base_entity(base, project)
45
+ _gen_enum_member_extensions(base, project)
46
+ _gen_role(base, project)
47
+ _gen_user(base, project)
48
+ _gen_user_role(base, project)
49
+ _gen_global_usings(base, project)
50
+
51
+ ui.file_created("Domain/ structure and files")
52
+
53
+
54
+ def generate_infrastructure_layer(base: Path, project: str):
55
+ folders = [
56
+ "Infrastructure/BackgroundServices",
57
+ "Infrastructure/Configurations",
58
+ "Infrastructure/Options",
59
+ "Infrastructure/Services",
60
+ ]
61
+ for folder in folders:
62
+ (base / folder).mkdir(parents=True, exist_ok=True)
63
+
64
+ ui.file_created("Infrastructure/ structure and files")
65
+
66
+
67
+ def generate_persistence_layer(base: Path, project: str):
68
+ folders = [
69
+ "Persistence/Configurations",
70
+ "Persistence/Extensions",
71
+ "Persistence/Migrations",
72
+ "Persistence/Seeders",
73
+ ]
74
+ for folder in folders:
75
+ (base / folder).mkdir(parents=True, exist_ok=True)
76
+
77
+ _gen_data_context(base, project)
78
+ _gen_role_seeder(base, project)
79
+ _gen_user_seeder(base, project)
80
+ _gen_user_role_configuration(base, project)
81
+
82
+ ui.file_created("Persistence/ structure and files")
83
+
84
+
85
+ # ── Application files ──────────────────────────────────────────────────────────
86
+
87
+ def _gen_validation_behavior(base: Path, project: str):
88
+ content = f"""using FluentValidation;
89
+ using MediatR;
90
+
91
+ namespace {project}.Application.Common.Behaviors
92
+ {{
93
+ public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
94
+ where TRequest : notnull
95
+ {{
96
+ private readonly IEnumerable<IValidator<TRequest>> _validators;
97
+
98
+ public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
99
+ {{
100
+ _validators = validators;
101
+ }}
102
+
103
+ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
104
+ {{
105
+ if (_validators.Any())
106
+ {{
107
+ var context = new ValidationContext<TRequest>(request);
108
+ var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
109
+ var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
110
+ if (failures.Count != 0)
111
+ throw new ValidationException(failures);
112
+ }}
113
+ return await next();
114
+ }}
115
+ }}
116
+ }}
117
+ """
118
+ (base / "Application/Common/Behaviors/ValidationBehavior.cs").write_text(content)
119
+
120
+
121
+ def _gen_error_messages(base: Path, project: str):
122
+ content = f"""namespace {project}.Application.Common.Constants
123
+ {{
124
+ public static class ErrorMessages
125
+ {{
126
+ public const string NotFound = "The requested resource was not found.";
127
+ public const string Unauthorized = "You are not authorized to perform this action.";
128
+ public const string BadRequest = "The request is invalid.";
129
+ public const string InternalServerError = "An unexpected error occurred.";
130
+ }}
131
+ }}
132
+ """
133
+ (base / "Application/Common/Constants/ErrorMessages.cs").write_text(content)
134
+
135
+
136
+ def _gen_app_exceptions(base: Path, project: str):
137
+ content = f"""namespace {project}.Application.Common.Exceptions
138
+ {{
139
+ public class AppException : Exception
140
+ {{
141
+ public int StatusCode {{ get; }}
142
+
143
+ public AppException(string message, int statusCode = 400) : base(message)
144
+ {{
145
+ StatusCode = statusCode;
146
+ }}
147
+ }}
148
+ }}
149
+ """
150
+ (base / "Application/Common/Exceptions/AppExceptions.cs").write_text(content)
151
+
152
+
153
+ def _gen_not_found_exception(base: Path, project: str):
154
+ content = f"""namespace {project}.Application.Common.Exceptions
155
+ {{
156
+ public class NotFoundException : Exception
157
+ {{
158
+ public NotFoundException(string name, object key)
159
+ : base($"{{name}} ({{key}}) was not found.")
160
+ {{
161
+ }}
162
+ }}
163
+ }}
164
+ """
165
+ (base / "Application/Common/Exceptions/NotFoundException.cs").write_text(content)
166
+
167
+
168
+ def _gen_api_response(base: Path, project: str):
169
+ content = f"""namespace {project}.Application.Common.Models
170
+ {{
171
+ public class ApiResponse<T>
172
+ {{
173
+ public bool Success {{ get; set; }}
174
+ public string? Message {{ get; set; }}
175
+ public T? Data {{ get; set; }}
176
+
177
+ public static ApiResponse<T> Ok(T data, string message = "Success") =>
178
+ new() {{ Success = true, Message = message, Data = data }};
179
+
180
+ public static ApiResponse<T> Fail(string message) =>
181
+ new() {{ Success = false, Message = message }};
182
+ }}
183
+ }}
184
+ """
185
+ (base / "Application/Common/Models/ApiResponse.cs").write_text(content)
186
+
187
+
188
+ def _gen_result(base: Path, project: str):
189
+ content = f"""namespace {project}.Application.Common.Models
190
+ {{
191
+ public class Result
192
+ {{
193
+ public bool IsSuccess {{ get; }}
194
+ public string? Error {{ get; }}
195
+
196
+ protected Result(bool isSuccess, string? error)
197
+ {{
198
+ IsSuccess = isSuccess;
199
+ Error = error;
200
+ }}
201
+
202
+ public static Result Success() => new(true, null);
203
+ public static Result Failure(string error) => new(false, error);
204
+ }}
205
+
206
+ public class Result<T> : Result
207
+ {{
208
+ public T? Value {{ get; }}
209
+
210
+ protected Result(bool isSuccess, T? value, string? error) : base(isSuccess, error)
211
+ {{
212
+ Value = value;
213
+ }}
214
+
215
+ public static Result<T> Success(T value) => new(true, value, null);
216
+ public static new Result<T> Failure(string error) => new(false, default, error);
217
+ }}
218
+ }}
219
+ """
220
+ (base / "Application/Common/Models/Result.cs").write_text(content)
221
+
222
+
223
+ def _gen_paging_params(base: Path, project: str):
224
+ content = f"""namespace {project}.Application.Common.Models
225
+ {{
226
+ public class PagingParams
227
+ {{
228
+ public int PageNumber {{ get; set; }} = 1;
229
+ public int PageSize {{ get; set; }} = 10;
230
+ }}
231
+ }}
232
+ """
233
+ (base / "Application/Common/Models/PagingParams.cs").write_text(content)
234
+
235
+
236
+ def _gen_paginated_result(base: Path, project: str):
237
+ content = f"""namespace {project}.Application.Common.Models
238
+ {{
239
+ public class PaginatedResult<T>
240
+ {{
241
+ public List<T> Items {{ get; set; }} = new();
242
+ public int TotalCount {{ get; set; }}
243
+ public int PageNumber {{ get; set; }}
244
+ public int PageSize {{ get; set; }}
245
+ public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
246
+ public bool HasPreviousPage => PageNumber > 1;
247
+ public bool HasNextPage => PageNumber < TotalPages;
248
+ }}
249
+ }}
250
+ """
251
+ (base / "Application/Common/Models/PaginatedResult.cs").write_text(content)
252
+
253
+
254
+ def _gen_paged_list(base: Path, project: str):
255
+ content = f"""using Microsoft.EntityFrameworkCore;
256
+
257
+ namespace {project}.Application.Common.Models
258
+ {{
259
+ public class PagedList<T>
260
+ {{
261
+ public List<T> Items {{ get; }}
262
+ public int TotalCount {{ get; }}
263
+ public int PageNumber {{ get; }}
264
+ public int PageSize {{ get; }}
265
+ public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
266
+ public bool HasPreviousPage => PageNumber > 1;
267
+ public bool HasNextPage => PageNumber < TotalPages;
268
+
269
+ private PagedList(List<T> items, int totalCount, int pageNumber, int pageSize)
270
+ {{
271
+ Items = items;
272
+ TotalCount = totalCount;
273
+ PageNumber = pageNumber;
274
+ PageSize = pageSize;
275
+ }}
276
+
277
+ public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
278
+ {{
279
+ var totalCount = await source.CountAsync();
280
+ var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
281
+ return new PagedList<T>(items, totalCount, pageNumber, pageSize);
282
+ }}
283
+ }}
284
+ }}
285
+ """
286
+ (base / "Application/Common/Models/PagedList.cs").write_text(content)
287
+
288
+
289
+ def _gen_paged_response(base: Path, project: str):
290
+ content = f"""namespace {project}.Application.Common.Models
291
+ {{
292
+ public class PagedResponse<T>
293
+ {{
294
+ public List<T> Items {{ get; set; }} = new();
295
+ public int TotalCount {{ get; set; }}
296
+ public int PageNumber {{ get; set; }}
297
+ public int PageSize {{ get; set; }}
298
+ public int TotalPages {{ get; set; }}
299
+ public bool HasPreviousPage {{ get; set; }}
300
+ public bool HasNextPage {{ get; set; }}
301
+ }}
302
+ }}
303
+ """
304
+ (base / "Application/Common/Models/PagedResponse.cs").write_text(content)
305
+
306
+
307
+ def _gen_iso_datetime(base: Path, project: str):
308
+ content = f"""using System.Text.Json;
309
+ using System.Text.Json.Serialization;
310
+
311
+ namespace {project}.Application.Common.Models.Serialization
312
+ {{
313
+ public class IsoDateTimeConverter : JsonConverter<DateTime>
314
+ {{
315
+ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
316
+ => DateTime.Parse(reader.GetString()!);
317
+
318
+ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
319
+ => writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"));
320
+ }}
321
+ }}
322
+ """
323
+ (base / "Application/Common/Models/Serialization/IsoDateTime.cs").write_text(content)
324
+
325
+
326
+ # ── Domain files ───────────────────────────────────────────────────────────────
327
+
328
+ def _gen_base_entity(base: Path, project: str):
329
+ content = f"""namespace {project}.Domain.Common
330
+ {{
331
+ public abstract class BaseEntity
332
+ {{
333
+ public Guid Id {{ get; set; }} = Guid.NewGuid();
334
+ public DateTime CreatedAt {{ get; set; }} = DateTime.UtcNow;
335
+ public DateTime UpdatedAt {{ get; set; }} = DateTime.UtcNow;
336
+ }}
337
+ }}
338
+ """
339
+ (base / "Domain/Common/BaseEntity.cs").write_text(content)
340
+
341
+
342
+ def _gen_enum_member_extensions(base: Path, project: str):
343
+ content = f"""using System.Runtime.Serialization;
344
+ using System.Reflection;
345
+
346
+ namespace {project}.Domain.Common
347
+ {{
348
+ public static class EnumMemberExtensions
349
+ {{
350
+ public static string ToEnumMemberValue<T>(this T value) where T : Enum
351
+ {{
352
+ return typeof(T)
353
+ .GetField(value.ToString())
354
+ ?.GetCustomAttribute<EnumMemberAttribute>()
355
+ ?.Value ?? value.ToString();
356
+ }}
357
+ }}
358
+ }}
359
+ """
360
+ (base / "Domain/Common/EnumMemberExtensions.cs").write_text(content)
361
+
362
+
363
+ def _gen_role(base: Path, project: str):
364
+ content = f"""using Microsoft.AspNetCore.Identity;
365
+
366
+ namespace {project}.Domain.Entities.Identity
367
+ {{
368
+ public class Role : IdentityRole<Guid>
369
+ {{
370
+ public ICollection<UserRole> UserRoles {{ get; set; }} = new List<UserRole>();
371
+ }}
372
+ }}
373
+ """
374
+ (base / "Domain/Entities/Identity/Role.cs").write_text(content)
375
+
376
+
377
+ def _gen_user(base: Path, project: str):
378
+ content = f"""using Microsoft.AspNetCore.Identity;
379
+
380
+ namespace {project}.Domain.Entities.Identity
381
+ {{
382
+ public class User : IdentityUser<Guid>
383
+ {{
384
+ public string FirstName {{ get; set; }} = string.Empty;
385
+ public string LastName {{ get; set; }} = string.Empty;
386
+ public DateTime CreatedAt {{ get; set; }} = DateTime.UtcNow;
387
+ public DateTime UpdatedAt {{ get; set; }} = DateTime.UtcNow;
388
+ public ICollection<UserRole> UserRoles {{ get; set; }} = new List<UserRole>();
389
+ }}
390
+ }}
391
+ """
392
+ (base / "Domain/Entities/Identity/User.cs").write_text(content)
393
+
394
+
395
+ def _gen_user_role(base: Path, project: str):
396
+ content = f"""using Microsoft.AspNetCore.Identity;
397
+
398
+ namespace {project}.Domain.Entities.Identity
399
+ {{
400
+ public class UserRole : IdentityUserRole<Guid>
401
+ {{
402
+ public virtual User User {{ get; set; }} = null!;
403
+ public virtual Role Role {{ get; set; }} = null!;
404
+ }}
405
+ }}
406
+ """
407
+ (base / "Domain/Entities/Identity/UserRole.cs").write_text(content)
408
+
409
+
410
+ def _gen_global_usings(base: Path, project: str):
411
+ content = f"""global using {project}.Domain.Common;
412
+ global using {project}.Domain.Entities;
413
+ """
414
+ (base / "Domain/GlobalUsings.cs").write_text(content)
415
+
416
+
417
+ # ── Persistence files ──────────────────────────────────────────────────────────
418
+
419
+ def _gen_data_context(base: Path, project: str):
420
+ identity_db_context = "IdentityDbContext<"
421
+ content = f"""using {project}.Domain.Entities.Identity;
422
+ using Humanizer;
423
+ using Microsoft.AspNetCore.Identity;
424
+ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
425
+ using Microsoft.EntityFrameworkCore;
426
+ using System.Reflection;
427
+
428
+ namespace {project}.Persistence
429
+ {{
430
+ public class DataContext : {identity_db_context}
431
+ User,
432
+ Role,
433
+ Guid,
434
+ IdentityUserClaim<Guid>,
435
+ UserRole,
436
+ IdentityUserLogin<Guid>,
437
+ IdentityRoleClaim<Guid>,
438
+ IdentityUserToken<Guid>>
439
+ {{
440
+ public DataContext(DbContextOptions<DataContext> options) : base(options)
441
+ {{
442
+ }}
443
+
444
+ protected override void OnModelCreating(ModelBuilder builder)
445
+ {{
446
+ base.OnModelCreating(builder);
447
+ builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
448
+ foreach (var entityType in builder.Model.GetEntityTypes())
449
+ {{
450
+ var table = entityType.GetTableName();
451
+ if (table!.StartsWith("AspNet"))
452
+ entityType.SetTableName(table.Substring(6).Underscore());
453
+ }}
454
+ }}
455
+ }}
456
+ }}
457
+ """
458
+ (base / "Persistence/DataContext.cs").write_text(content)
459
+
460
+
461
+ def _gen_role_seeder(base: Path, project: str):
462
+ content = f"""using {project}.Domain.Entities.Identity;
463
+ using Microsoft.AspNetCore.Identity;
464
+
465
+ namespace {project}.Persistence.Seeders
466
+ {{
467
+ public static class RoleSeeder
468
+ {{
469
+ public static async Task SeedAsync(RoleManager<Role> roleManager)
470
+ {{
471
+ string[] roles = {{ "SuperAdmin", "Admin", "User" }};
472
+
473
+ foreach (var role in roles)
474
+ {{
475
+ if (!await roleManager.RoleExistsAsync(role))
476
+ await roleManager.CreateAsync(new Role {{ Name = role }});
477
+ }}
478
+ }}
479
+ }}
480
+ }}
481
+ """
482
+ (base / "Persistence/Seeders/RoleSeeder.cs").write_text(content)
483
+
484
+
485
+ def _gen_user_seeder(base: Path, project: str):
486
+ content = f"""using {project}.Domain.Entities.Identity;
487
+ using Microsoft.AspNetCore.Identity;
488
+
489
+ namespace {project}.Persistence.Seeders
490
+ {{
491
+ public static class UserSeeder
492
+ {{
493
+ public static async Task SeedAsync(UserManager<User> userManager)
494
+ {{
495
+ var superAdmin = await userManager.FindByEmailAsync("super_admin@{project.lower()}.com");
496
+
497
+ if (superAdmin == null)
498
+ {{
499
+ var user = new User
500
+ {{
501
+ UserName = "super_admin@{project.lower()}.com",
502
+ Email = "super_admin@{project.lower()}.com",
503
+ FirstName = "Super",
504
+ LastName = "Admin",
505
+ EmailConfirmed = true,
506
+ }};
507
+
508
+ await userManager.CreateAsync(user, "Pa$$w0rd");
509
+ await userManager.AddToRoleAsync(user, "SuperAdmin");
510
+ }}
511
+ }}
512
+ }}
513
+ }}
514
+ """
515
+ (base / "Persistence/Seeders/UserSeeder.cs").write_text(content)
516
+
517
+
518
+ def _gen_user_role_configuration(base: Path, project: str):
519
+ content = f"""using {project}.Domain.Entities.Identity;
520
+ using Microsoft.EntityFrameworkCore;
521
+ using Microsoft.EntityFrameworkCore.Metadata.Builders;
522
+
523
+ namespace {project}.Persistence.Configurations
524
+ {{
525
+ public class UserRoleConfiguration : IEntityTypeConfiguration<UserRole>
526
+ {{
527
+ public void Configure(EntityTypeBuilder<UserRole> builder)
528
+ {{
529
+ builder.HasKey(ur => new {{ ur.UserId, ur.RoleId }});
530
+
531
+ builder.HasOne(ur => ur.User)
532
+ .WithMany(u => u.UserRoles)
533
+ .HasForeignKey(ur => ur.UserId)
534
+ .IsRequired();
535
+
536
+ builder.HasOne(ur => ur.Role)
537
+ .WithMany(r => r.UserRoles)
538
+ .HasForeignKey(ur => ur.RoleId)
539
+ .IsRequired();
540
+ }}
541
+ }}
542
+ }}
543
+ """
544
544
  (base / "Persistence/Configurations/UserRoleConfiguration.cs").write_text(content)