modules/backend/Write-WebApiLayer.ps1
|
param( [string]$ProjectName, [string]$OrmMode, [string]$DbProvider, [string]$OutputPath, [string[]]$Entities, [bool]$Observability = $false ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") $base = Join-Path $OutputPath "src\$ProjectName.WebApi" function Write-File($rel, $con) { Write-CoreFile (Join-Path $base $rel) $con } function T($t, $e = "") { $res = $t.Replace("{P}", $ProjectName) if ($e) { $res = $res.Replace("{E}", $e) } return $res } if ($Observability) { Write-File "Configurations\OpenTelemetryConfiguration.cs" (T @' using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Resources; using OpenTelemetry.Trace; namespace {P}.WebApi.Configurations; public static class OpenTelemetryConfiguration { public static void AddOpenTelemetryConfiguration(this IServiceCollection services) { services.AddOpenTelemetry() .WithTracing(tracing => tracing .AddConsoleExporter() .SetResourceBuilder(ResourceBuilder.CreateDefault() .AddService("{P}Api"))); } } '@) } # ===== Program.cs ===== $obsUsings = if ($Observability) { "using {P}.Infrastructure.Configurations;`nusing {P}.WebApi.Configurations;" } else { "" } $obsBuilder = if ($Observability) { "`nbuilder.Host.AddSerilogConfiguration();`n" } else { "" } $obsSrv = if ($Observability) { "`nbuilder.Services.AddOpenTelemetryConfiguration();" } else { "" } # Health check registration — EFCore/Hybrid has DB context check, Dapper has basic check $hcRegistration = if ($OrmMode -match "EFCore|Hybrid") { "builder.Services.AddHealthChecks()`n .AddDbContextCheck<AppDbContext>(name: `"database`");" } else { "builder.Services.AddHealthChecks();" } $hcUsing = if ($OrmMode -match "EFCore|Hybrid") { "using {P}.Persistence.Context;" } else { "" } Write-File "Program.cs" (T @" using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; $obsUsings $hcUsing using {P}.Application.ServiceRegistration; using {P}.Infrastructure.ServiceRegistration; using {P}.Persistence.ServiceRegistration; using {P}.WebApi.Middleware; var builder = WebApplication.CreateBuilder(args); $obsBuilder builder.Services.AddControllers(); builder.Services.AddApplicationServices(); builder.Services.AddInfrastructureServices(builder.Configuration); builder.Services.AddPersistenceServices(builder.Configuration); $obsSrv $hcRegistration builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); builder.Services.AddProblemDetails(); builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); options.AddPolicy("UserOnly", policy => policy.RequireRole("User", "Admin")); }); builder.Services.AddCors(opt => opt.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader())); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "{P} API", Version = "v1" }); c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Name = "Authorization", Type = SecuritySchemeType.ApiKey, Scheme = "Bearer" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty<string>() } }); }); var app = builder.Build(); app.UseExceptionHandler(); app.UseMiddleware<ExceptionMiddleware>(); app.UseCors("AllowAll"); app.UseSwagger(); app.UseSwaggerUI(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapHealthChecks("/health"); app.Run(); public partial class Program { } "@) # ===== appsettings.json ===== $connStrings = switch ($DbProvider) { "Postgres" { @{ Docker = "Host=db;Database={P}Db;Username=postgres;Password=postgres" Local = "Host=localhost;Database={P}Db;Username=postgres;Password=postgres" } } "SQLite" { @{ Docker = "Data Source=/app/data/{P}.db" Local = "Data Source={P}.db" } } default { @{ Docker = "Server=db,1433;Database={P}Db;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;" Local = "Server=localhost,1433;Database={P}Db;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;" } } } Write-File "appsettings.json" (T @" { "ConnectionStrings": { "DefaultConnection": "$($connStrings.Docker)" }, "Jwt": { "Key": "your-super-secret-key-min-32-characters!!", "Issuer": "{P}Api", "Audience": "{P}Client" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" } "@) Write-File "appsettings.Development.json" (T @" { "ConnectionStrings": { "DefaultConnection": "$($connStrings.Local)" } } "@) # ===== Middleware & Common Controllers ===== Write-File "Middleware\GlobalExceptionHandler.cs" (T @' using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; using FluentValidation; namespace {P}.WebApi.Middleware; public class GlobalExceptionHandler : IExceptionHandler { public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken ct) { var (status, title) = exception switch { ValidationException => (StatusCodes.Status400BadRequest, "Validation Error"), UnauthorizedAccessException => (StatusCodes.Status401Unauthorized, "Unauthorized"), _ => (StatusCodes.Status500InternalServerError, "Internal Server Error") }; context.Response.StatusCode = status; await context.Response.WriteAsJsonAsync(new ProblemDetails { Status = status, Title = title, Detail = exception.Message }, ct); return true; } } '@) Write-File "Middleware\ExceptionMiddleware.cs" (T @' using System.Net; using System.Text.Json; using FluentValidation; namespace {P}.WebApi.Middleware; public class ExceptionMiddleware { private readonly RequestDelegate _next; public ExceptionMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonSerializer.Serialize(new { message = ex.Message })); } } } '@) foreach ($e in $Entities) { if ($e -eq "User") { Write-File "Controllers\AuthController.cs" (T @' using MediatR; using Microsoft.AspNetCore.Mvc; using {P}.Application.Features.Auth.Commands.Login; namespace {P}.WebApi.Controllers; [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly IMediator _mediator; public AuthController(IMediator mediator) => _mediator = mediator; [HttpPost("login")] public async Task<IActionResult> Login([FromBody] LoginCommand command) => Ok(await _mediator.Send(command)); } '@) } else { $pE = Get-Plural $e Write-File "Controllers\$pE`Controller.cs" (T @' using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using {P}.Application.Common; using {P}.Application.Features.{PE}.Commands.Add{E}; using {P}.Application.Features.{PE}.Queries.GetAll{E}s; using {P}.Application.Features.{PE}.Queries.GetPaged{E}s; namespace {P}.WebApi.Controllers; [ApiController] [Route("api/[controller]")] [Authorize] public class {PE}Controller : ControllerBase { private readonly IMediator _mediator; public {PE}Controller(IMediator mediator) => _mediator = mediator; [HttpGet] public async Task<IActionResult> GetAll() => Ok(ApiResponse<object>.Ok(await _mediator.Send(new GetAll{E}sQuery()))); [HttpGet("paged")] public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] int size = 20) => Ok(ApiResponse<object>.Ok(await _mediator.Send(new GetPaged{E}sQuery(page, size)))); [HttpPost] public async Task<IActionResult> Add([FromBody] Add{E}Command cmd) => Ok(ApiResponse<object>.Ok(new { id = await _mediator.Send(cmd) })); } '@ $e | % { $_.Replace("{PE}", $pE) }) } } # ===== Dockerfile & Launch ===== Write-File "Dockerfile" (T @' FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 8080 RUN apt-get update && apt-get install -y --no-install-recommends wget && rm -rf /var/lib/apt/lists/* FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY . . RUN dotnet restore "src/{P}.WebApi/{P}.WebApi.csproj" RUN dotnet publish "src/{P}.WebApi/{P}.WebApi.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "{P}.WebApi.dll"] '@) Write-File "Properties\launchSettings.json" (T @' { "profiles": { "{P}.WebApi": { "commandName": "Project", "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } '@) |