modules/backend/Write-WebApiLayer.ps1

param(
    [string]$ProjectName,
    [string]$OrmMode,
    [string]$DbProvider,
    [string]$OutputPath,
    [string[]]$Entities,
    [bool]$Observability = $false,
    [string]$ConnectionString = ""
)

. (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;"
        }
    }
}

if ($ConnectionString) {
    $connStrings.Local = $ConnectionString
    $connStrings.Docker = $ConnectionString
}

Write-File "appsettings.json" (T @"
{
  "ConnectionStrings": { "DefaultConnection": "$($connStrings.Local)" },
  "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{PE};
using {P}.Application.Features.{PE}.Queries.GetPaged{PE};
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{PE}Query())));
    [HttpGet("paged")] public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] int size = 20) => Ok(ApiResponse<object>.Ok(await _mediator.Send(new GetPaged{PE}Query(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" } } } }
'@
)