Files
SchotzIAD/backend/TrajetKmTracker.Api/Program.cs
T
MadJax e3f946f7d4 SchotzIAD KM Tracker : suivi des trajets kilométriques (Angular + .NET 8)
Application complète de saisie et de suivi des trajets kilométriques avec
calcul de distance routière (OpenRouteService), barème kilométrique, résumés
et régularisation annuelle.

Backend (ASP.NET Core .NET 8, EF Core, SQLite) :
- Trajets (CRUD, réordonnancement, insertion), calcul de distance ORS avec
  fallback Haversine, géométrie d'itinéraire stockée par trajet, recalcul en masse.
- Profil, véhicules (avec dénomination), carnet d'adresses nommées, cache d'adresses.
- Barème kilométrique : tranches multiples par puissance fiscale, borne max
  exclusive avec 'illimité' (KmMax nullable), coefficient à 3 décimales,
  recopie du dernier barème connu.
- Résumés mensuel/annuel, montants réglés, régularisation, export PDF.

Frontend (Angular 22, zoneless + signals) :
- Consultation par jour, navigation temporelle synchronisée, carte Leaflet,
  formulaire de trajet (adresses nommées, domicile, autocomplétion BAN).
- Détection de changement zoneless (indépendante de zone.js).

Hébergement : Docker Compose (nginx + backend), scripts run-dev.sh / run-docker.sh.
2026-09-14 02:08:27 +02:00

175 lines
6.5 KiB
C#

using FluentValidation;
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TrajetKmTracker.Api.Data;
using TrajetKmTracker.Api.Validators;
using TrajetKmTracker.Api.Configuration;
using TrajetKmTracker.Api.Repositories;
using TrajetKmTracker.Api.Services;
// Licence QuestPDF (usage communautaire, gratuit) — requise avant toute génération de PDF.
QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community;
var builder = WebApplication.CreateBuilder(args);
// --- Options de configuration liées depuis appsettings.json ---
builder.Services.Configure<BANOptions>(
builder.Configuration.GetSection(BANOptions.SectionName));
builder.Services.Configure<ORSOptions>(
builder.Configuration.GetSection(ORSOptions.SectionName));
// --- Client typé BAN (Base Adresses Nationale) ---
// BaseAddress et Timeout proviennent de BANOptions (appsettings.json).
var banOptions = builder.Configuration
.GetSection(BANOptions.SectionName)
.Get<BANOptions>() ?? new BANOptions();
builder.Services.AddHttpClient<IBANClient, BANClient>(client =>
{
client.BaseAddress = new Uri(banOptions.BaseUrl);
client.Timeout = TimeSpan.FromSeconds(banOptions.TimeoutSeconds);
});
// --- Client typé ORS (OpenRouteService) ---
// BaseAddress, Timeout et en-tête de clé d'API proviennent de ORSOptions (appsettings.json).
var orsOptions = builder.Configuration
.GetSection(ORSOptions.SectionName)
.Get<ORSOptions>() ?? new ORSOptions();
builder.Services.AddHttpClient<IORSClient, ORSClient>(client =>
{
if (!string.IsNullOrWhiteSpace(orsOptions.BaseUrl))
{
// Le slash final garantit la résolution correcte des chemins relatifs.
var baseUrl = orsOptions.BaseUrl.EndsWith('/')
? orsOptions.BaseUrl
: orsOptions.BaseUrl + "/";
client.BaseAddress = new Uri(baseUrl);
}
client.Timeout = TimeSpan.FromSeconds(orsOptions.TimeoutSeconds);
if (!string.IsNullOrWhiteSpace(orsOptions.ApiKey))
{
// ORS attend la clé brute dans l'en-tête Authorization (sans schéma « Bearer »).
// Les clés ORS (JWT) contiennent des caractères refusés par la validation stricte
// de DefaultRequestHeaders.Add ; on utilise TryAddWithoutValidation pour l'insérer
// telle quelle et éviter une FormatException à la création du client.
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", orsOptions.ApiKey);
}
});
// --- Calculateur de distance (orchestration BAN + ORS + fallback Haversine) ---
builder.Services.AddScoped<IDistanceCalculator, DistanceCalculatorService>();
// --- Cache des adresses géocodées ---
builder.Services.AddScoped<IAdresseCacheRepository, AdresseCacheRepository>();
builder.Services.AddScoped<IAdresseCacheService, AdresseCacheService>();
// --- Services de base ---
builder.Services.AddControllers();
// --- Health checks (endpoint GET /health, utilisé par Docker Compose) ---
builder.Services.AddHealthChecks();
// --- Validation des requêtes (FluentValidation) ---
// Enregistre tous les validateurs de l'assembly et active la validation automatique côté MVC.
builder.Services.AddValidatorsFromAssemblyContaining<CreateTrajetDtoValidator>();
builder.Services.AddFluentValidationAutoValidation();
// --- Base de données (EF Core + SQLite) ---
// La chaîne de connexion provient de la configuration ("ConnectionStrings:Default").
var connectionString = builder.Configuration.GetConnectionString("Default")
?? "Data Source=trajets.db";
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(connectionString));
// --- Repositories & services métier ---
builder.Services.AddScoped<TrajetKmTracker.Api.Repositories.IVehiculeRepository,
TrajetKmTracker.Api.Repositories.VehiculeRepository>();
builder.Services.AddScoped<IVehiculeService, VehiculeService>();
builder.Services.AddScoped<IProfilService, ProfilService>();
builder.Services.AddScoped<IAdresseNommeeService, AdresseNommeeService>();
builder.Services.AddScoped<IBaremeService, BaremeService>();
builder.Services.AddScoped<IMontantRegleService, MontantRegleService>();
builder.Services.AddScoped<IRemboursementService, RemboursementService>();
builder.Services.AddScoped<ITotauxService, TotauxService>();
builder.Services.AddScoped<ITrajetRepository, TrajetRepository>();
builder.Services.AddScoped<ITrajetService, TrajetService>();
builder.Services.AddScoped<IResumeService, ResumeService>();
builder.Services.AddScoped<IPdfService, PdfService>();
// Exploration des endpoints + Swagger / OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "SchotzIAD KM Tracker API",
Version = "v1",
Description = "API de saisie, calcul et consultation des trajets kilométriques."
});
});
// ProblemDetails : réponses d'erreur normalisées (RFC 7807)
builder.Services.AddProblemDetails();
// CORS pour le frontend Angular.
// Les origines autorisées sont configurables via appsettings ("Cors:AllowedOrigins").
const string CorsPolicyName = "AngularFrontend";
var allowedOrigins = builder.Configuration
.GetSection("Cors:AllowedOrigins")
.Get<string[]>() ?? new[] { "http://localhost:4200" };
builder.Services.AddCors(options =>
{
options.AddPolicy(CorsPolicyName, policy =>
{
policy.WithOrigins(allowedOrigins)
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
// --- Pipeline HTTP ---
// Gestion centralisée des exceptions -> ProblemDetails
app.UseExceptionHandler();
app.UseStatusCodePages();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors(CorsPolicyName);
app.UseAuthorization();
app.MapControllers();
// Endpoint de santé — utilisé par le healthcheck Docker Compose (depends_on: service_healthy).
app.MapHealthChecks("/health");
// --- Application automatique des migrations EF Core au démarrage ---
// Au premier lancement, la base (ex. /data/trajets.db sur le volume monté) est créée ;
// aux lancements suivants, les migrations en attente sont appliquées sans intervention manuelle.
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await dbContext.Database.MigrateAsync();
}
app.Run();
// Exposé pour les tests d'intégration (WebApplicationFactory<Program>).
public partial class Program { }