1
0
voyager-api/ScrapperAPI/Program.cs

123 lines
4.3 KiB
C#

using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using ScrapperAPI.Bus;
using ScrapperAPI.Factories;
using ScrapperAPI.Hub;
using ScrapperAPI.Interfaces;
using ScrapperAPI.Options;
using ScrapperAPI.Repositories;
using ScrapperAPI.Services;
using ScrapperAPI.Utils;
using ScrapperAPI.Workers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.AddSignalR();
builder.Services.AddControllers();
// Authentik (OIDC) - JWT Bearer validation for API + SignalR
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// Example: https://auth.seu-dominio.com/application/o/seu-app/
options.Authority = builder.Configuration["Authentication:Authority"];
options.RequireHttpsMetadata = builder.Configuration.GetValue("Authentication:RequireHttpsMetadata", true);
// Usually the SPA client_id
options.Audience = builder.Configuration["Authentication:Audience"];
// SignalR sends the token via the query string (access_token) for WebSockets
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/ws/scrape"))
{
context.Token = accessToken;
}
return System.Threading.Tasks.Task.CompletedTask;
}
};
// If you want stricter token validation, uncomment:
// options.TokenValidationParameters = new TokenValidationParameters
// {
// ValidateIssuer = true,
// ValidateAudience = true,
// };
});
builder.Services.AddAuthorization(options =>
{
// Require authentication by default for ALL endpoints (controllers + hub)
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
builder.Services.Configure<ScraperOptions>(builder.Configuration.GetSection("Scraper"));
builder.Services.Configure<ExtractionOptions>(builder.Configuration.GetSection("Extraction"));
builder.Services.AddSingleton<IDomainRateLimiter>(sp =>
{
var opts = sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<ScraperOptions>>().Value;
return new DomainRateLimiter(opts.RateLimit.PerDomainMinDelayMs);
});
builder.Services.AddSingleton<IScrapeEventBus, SignalRScrapeEventBus>();
builder.Services.AddSingleton<IScraperHttpClient, ScraperHttpClient>();
builder.Services.AddSingleton<IDbConnectionFactory, NpgsqlConnectionFactory>();
builder.Services.AddScoped<ISessionRepository, SessionRepository>();
builder.Services.AddScoped<IQueueRepository, QueueRepository>();
builder.Services.AddScoped<IContentRepository, ContentRepository>();
// Extraction
builder.Services.AddSingleton<ExtractionEngine>();
builder.Services.AddScoped<IExtractionModelRepository, ExtractionModelRepository>();
builder.Services.AddScoped<IExtractionRunRepository, ExtractionRunRepository>();
builder.Services.AddScoped<IExtractedDataRepository, ExtractedDataRepository>();
builder.Services.AddHttpClient("scraper", c => c.Timeout = TimeSpan.FromSeconds(30));
builder.Services.AddSingleton<IScrapeCoordinator, ScrapeCoordinator>();
builder.Services.AddHostedService(sp => (ScrapeCoordinator)sp.GetRequiredService<IScrapeCoordinator>());
builder.Services.AddSingleton<IExtractionCoordinator, ExtractionCoordinator>();
builder.Services.AddHostedService(sp => (ExtractionCoordinator)sp.GetRequiredService<IExtractionCoordinator>());
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowReact",
policy =>
{
policy.WithOrigins("http://localhost:3000")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
var app = builder.Build();
app.UseCors("AllowReact");
app.UseAuthentication();
app.UseAuthorization();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi().AllowAnonymous();
}
app.MapControllers();
app.MapHub<ScrapeHub>("/ws/scrape").RequireAuthorization();
// app.UseHttpsRedirection();
app.Run();