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.
175 lines
6.5 KiB
C#
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 { }
|