modules/backend/Write-ApplicationLayer.ps1
|
param( [string]$ProjectName, [string]$OrmMode, [string]$OutputPath, [string[]]$Entities, [string]$Language = 'tr' ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Get-Locale.ps1") $L = Get-Locale -Language $Language $base = Join-Path $OutputPath "src\$ProjectName.Application" function Write-File($rel, $con) { Write-CoreFile (Join-Path $base $rel) $con } function T($template, $e = "", $pE = "") { $res = $template.Replace("{P}", $ProjectName) if ($e) { $res = $res.Replace("{E}", $e) } if ($pE) { $res = $res.Replace("{PE}", $pE) } return $res } # ===== ApiResponse<T> Wrapper ===== Write-File "Common\ApiResponse.cs" (T @' namespace {P}.Application.Common; public class ApiResponse<T> { public bool Success { get; set; } public string? Message { get; set; } public T? Data { get; set; } public static ApiResponse<T> Ok(T data, string? message = null) => new() { Success = true, Data = data, Message = message }; public static ApiResponse<T> Fail(string message) => new() { Success = false, Message = message }; } '@) # ===== Common Interfaces ===== Write-File "Interfaces\ITokenService.cs" (T @' namespace {P}.Application.Interfaces; public interface ITokenService { string GenerateToken(int userId, string username, string role = "User"); } '@) Write-File "Interfaces\IPasswordHasher.cs" (T @' namespace {P}.Application.Interfaces; public interface IPasswordHasher { string Hash(string password); bool Verify(string password, string hash); } '@) # ===== Validation & Pipeline ===== Write-File "Behaviors\ValidationBehavior.cs" (T @' using FluentValidation; using MediatR; namespace {P}.Application.Behaviors; public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> { private readonly IEnumerable<IValidator<TRequest>> _validators; public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators; public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct) { if (!_validators.Any()) return await next(); var context = new ValidationContext<TRequest>(request); var failures = _validators.Select(v => v.Validate(context)).SelectMany(r => r.Errors).Where(e => e != null).ToList(); if (failures.Count != 0) throw new ValidationException(failures); return await next(); } } '@) # ===== Entity Scaffolding ===== foreach ($e in $Entities) { if ($e -eq "User") { # Auth feature — Command + Response + Handler in one file Write-File "Features\Auth\Commands\Login\LoginCommand.cs" (T @" using FluentValidation; using MediatR; using {P}.Application.Interfaces; using {P}.Domain.Interfaces; namespace {P}.Application.Features.Auth.Commands.Login; public record LoginCommand(string Username, string Password) : IRequest<LoginResponse>; public record LoginResponse(string Token, string Username); public class LoginCommandValidator : AbstractValidator<LoginCommand> { public LoginCommandValidator() { RuleFor(x => x.Username).NotEmpty().WithMessage("Username is required."); RuleFor(x => x.Password).NotEmpty().WithMessage("Password is required."); } } public class LoginCommandHandler : IRequestHandler<LoginCommand, LoginResponse> { private readonly IUserRepository _userRepo; private readonly ITokenService _tokenService; private readonly IPasswordHasher _hasher; public LoginCommandHandler(IUserRepository userRepo, ITokenService tokenService, IPasswordHasher hasher) { _userRepo = userRepo; _tokenService = tokenService; _hasher = hasher; } public async Task<LoginResponse> Handle(LoginCommand request, CancellationToken ct) { var user = await _userRepo.GetByUsernameAsync(request.Username) ?? throw new UnauthorizedAccessException("$($L.backendInvalidCreds)"); if (!_hasher.Verify(request.Password, user.PasswordHash)) throw new UnauthorizedAccessException("$($L.backendInvalidCreds)"); return new LoginResponse(_tokenService.GenerateToken(user.Id, user.Username, user.Role), user.Username); } } "@) } else { $pE = Get-Plural $e # Feature-root DTO $dtoExtra = if ($e -eq "Product") { ", decimal Price" } else { "" } Write-File "Features\$pE\$e`Dto.cs" (T @" namespace {P}.Application.Features.{PE}; public record {E}Dto(int Id, string Name, DateTime CreatedAt$dtoExtra); "@ $e $pE) # Feature-based AutoMapper profile Write-File "Features\$pE\Mappings\$e`Profile.cs" (T @' using AutoMapper; using {P}.Application.Features.{PE}; using {P}.Domain.Entities; namespace {P}.Application.Features.{PE}.Mappings; public class {E}Profile : Profile { public {E}Profile() { CreateMap<{E}, {E}Dto>(); } } '@ $e $pE) # Add Command — Command + Handler in one file $cmdExtra = if ($e -eq "Product") { ", decimal Price" } else { "" } $entitySet = if ($e -eq "Product") { "var entity = new {E}(request.Name, request.Price);" } else { "var entity = new {E}(request.Name);" } $validatorExtra = if ($e -eq "Product") { "`r`n RuleFor(x => x.Price).GreaterThan(0).WithMessage(`"Price must be greater than zero.`");" } else { "" } Write-File "Features\$pE\Commands\Add$e\Add$e`Command.cs" (T @" using FluentValidation; using MediatR; using {P}.Domain.Entities; using {P}.Domain.Interfaces; namespace {P}.Application.Features.{PE}.Commands.Add{E}; public record Add{E}Command(string Name$cmdExtra) : IRequest<int>; public class Add{E}CommandValidator : AbstractValidator<Add{E}Command> { public Add{E}CommandValidator() { RuleFor(x => x.Name).NotEmpty().WithMessage("Name is required.");$validatorExtra } } public class Add{E}CommandHandler : IRequestHandler<Add{E}Command, int> { private readonly I{E}Repository _repo; public Add{E}CommandHandler(I{E}Repository repo) => _repo = repo; public async Task<int> Handle(Add{E}Command request, CancellationToken ct) { $entitySet return await _repo.AddAsync(entity); } } "@ $e $pE) # GetAll Query — Query + Handler in one file Write-File "Features\$pE\Queries\GetAll$pE\GetAll$pE`Query.cs" (T @' using AutoMapper; using MediatR; using {P}.Application.Features.{PE}; using {P}.Domain.Interfaces; namespace {P}.Application.Features.{PE}.Queries.GetAll{PE}; public record GetAll{PE}Query : IRequest<IEnumerable<{E}Dto>>; public class GetAll{PE}QueryHandler : IRequestHandler<GetAll{PE}Query, IEnumerable<{E}Dto>> { private readonly I{E}Repository _repo; private readonly IMapper _mapper; public GetAll{PE}QueryHandler(I{E}Repository repo, IMapper mapper) { _repo = repo; _mapper = mapper; } public async Task<IEnumerable<{E}Dto>> Handle(GetAll{PE}Query request, CancellationToken ct) { var items = await _repo.GetAllAsync(); return _mapper.Map<IEnumerable<{E}Dto>>(items); } } '@ $e $pE) # GetPaged Query — Query + Handler in one file Write-File "Features\$pE\Queries\GetPaged$pE\GetPaged$pE`Query.cs" (T @' using AutoMapper; using MediatR; using {P}.Application.Features.{PE}; using {P}.Domain.Common; using {P}.Domain.Interfaces; namespace {P}.Application.Features.{PE}.Queries.GetPaged{PE}; public record GetPaged{PE}Query(int Page = 1, int Size = 20) : IRequest<PagedResult<{E}Dto>>; public class GetPaged{PE}QueryHandler : IRequestHandler<GetPaged{PE}Query, PagedResult<{E}Dto>> { private readonly I{E}Repository _repo; private readonly IMapper _mapper; public GetPaged{PE}QueryHandler(I{E}Repository repo, IMapper mapper) { _repo = repo; _mapper = mapper; } public async Task<PagedResult<{E}Dto>> Handle(GetPaged{PE}Query request, CancellationToken ct) { var pagedResult = await _repo.GetPagedAsync(new PagedQuery { Page = request.Page, Size = request.Size }); var dtos = _mapper.Map<IEnumerable<{E}Dto>>(pagedResult.Items); return new PagedResult<{E}Dto> { Items = dtos, TotalCount = pagedResult.TotalCount, Page = pagedResult.Page, Size = pagedResult.Size }; } } '@ $e $pE) } } # ===== ServiceRegistration ===== Write-File "ServiceRegistration\ApplicationServiceRegistration.cs" (T @' using System.Reflection; using FluentValidation; using MediatR; using Microsoft.Extensions.DependencyInjection; using {P}.Application.Behaviors; namespace {P}.Application.ServiceRegistration; public static class ApplicationServiceRegistration { public static IServiceCollection AddApplicationServices(this IServiceCollection services) { services.AddAutoMapper(typeof(ApplicationServiceRegistration).Assembly); services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); return services; } } '@) |