Files
idoklad/Program.cs
T
2026-06-15 08:42:56 +02:00

116 lines
4.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Idoklad.Client;
using Idoklad.Configuration;
using Idoklad.Credentials;
using Idoklad.Infrastructure;
using Idoklad.Services;
var builder = WebApplication.CreateBuilder(args);
// Configuration resolved from environment variables (single shared instance).
var settings = new IdokladSettings();
builder.Services.AddSingleton(settings);
// HttpClient for the iDoklad SDK, managed by IHttpClientFactory (recommended SDK usage).
builder.Services.AddHttpClient(DokladApiFactory.HttpClientName, client =>
{
client.Timeout = TimeSpan.FromSeconds(settings.RequestTimeoutSeconds);
});
builder.Services.AddHttpContextAccessor();
// Credential resolution + SDK client wiring.
builder.Services.AddScoped<RequestCredentialsProvider>();
builder.Services.AddScoped<DokladApiFactory>();
builder.Services.AddScoped<IdokladApiAccessor>();
// Agenda services.
builder.Services.AddScoped<ContactsService>();
builder.Services.AddScoped<IssuedInvoicesService>();
builder.Services.AddScoped<ReceivedInvoicesService>();
builder.Services.AddScoped<RegistersService>();
builder.Services.AddScoped<AccountService>();
builder.Services.AddScoped<CodeListsService>();
builder.Services.AddScoped<SalesDocumentsService>();
builder.Services.AddScoped<PurchaseCashService>();
builder.Services.AddScoped<PaymentsService>();
builder.Services.AddScoped<CatalogService>();
builder.Services.AddScoped<IntegrationService>();
builder.Services.AddScoped<StatisticsService>();
// Use Newtonsoft.Json so request/response binding matches the iDoklad SDK model attributes.
builder.Services
.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = settings.AppName,
Version = settings.AppVersion,
Description =
"REST integration with iDoklad built on the official IdokladSdk (.NET) 5.3.0, " +
"using the OAuth2 client credentials flow.\n\n" +
"**Credentials.** Every agenda endpoint needs an iDoklad ClientId, ClientSecret and ApplicationId. " +
"Sensitive values are required in request headers and are never accepted in the query string or body:\n\n" +
"- `X-ClientId` — iDoklad OAuth2 ClientId (required)\n" +
"- `X-ClientSecret` — iDoklad OAuth2 ClientSecret (required; sensitive; TLS only)\n" +
"- `X-ApplicationId` — OPTIONAL ApplicationId (GUID) for partner apps only; leave empty for a standard app\n" +
"- `X-Idoklad-Language` — optional response language (Cz, Sk, En)\n\n" +
"If a header is omitted, the matching environment default " +
"(`IDOKLAD_CLIENT_ID`, `IDOKLAD_CLIENT_SECRET`, `IDOKLAD_APPLICATION_ID`) is used. " +
"Only ClientId and ClientSecret are required; if either is missing the request is rejected with 401. " +
"When no ApplicationId is provided, authentication uses client_id + client_secret only.",
});
options.OperationFilter<CredentialHeadersOperationFilter>();
var xmlPath = Path.Combine(AppContext.BaseDirectory, $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml");
if (File.Exists(xmlPath))
{
options.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
}
});
var app = builder.Build();
if (!string.IsNullOrWhiteSpace(settings.RootPath))
{
// UsePathBase requires a leading slash; tolerate ROOT_PATH configured without one.
var basePath = settings.RootPath.StartsWith('/') ? settings.RootPath : "/" + settings.RootPath;
app.UsePathBase(basePath);
}
app.UseMiddleware<ExceptionHandlingMiddleware>();
// Serve the OpenAPI document under the same /docs prefix as the UI so a relative endpoint
// resolves correctly both locally and behind a reverse-proxy path base (ROOT_PATH).
app.UseSwagger(options =>
{
options.RouteTemplate = "docs/{documentName}/swagger.json";
// Advertise the request path base (e.g. /apps/idoklad behind the portal proxy) as the
// OpenAPI server so Swagger UI "Try it out" targets {pathBase}/contacts, not the host root.
options.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
var basePath = httpReq.PathBase.HasValue ? httpReq.PathBase.Value : "/";
swaggerDoc.Servers = new List<OpenApiServer> { new() { Url = basePath } };
});
});
app.UseSwaggerUI(options =>
{
// Interactive docs at /docs (matching the sibling microsoft-365-service).
options.RoutePrefix = "docs";
// Relative endpoint — resolves to {pathBase}/docs/v1/swagger.json in the browser.
options.SwaggerEndpoint("v1/swagger.json", $"{settings.AppName} v1");
options.DocumentTitle = $"{settings.AppName} API docs";
});
app.MapControllers();
app.Run();