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