All checks were successful
BCards Deployment Pipeline / Run Tests (pull_request) Successful in 3s
BCards Deployment Pipeline / PR Validation (pull_request) Successful in 0s
BCards Deployment Pipeline / Build and Push Image (pull_request) Has been skipped
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (pull_request) Has been skipped
BCards Deployment Pipeline / Deploy to Staging (x86 - Local) (pull_request) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (pull_request) Has been skipped
BCards Deployment Pipeline / Deployment Summary (pull_request) Successful in 0s
288 lines
9.3 KiB
C#
288 lines
9.3 KiB
C#
using BCards.Web.Configuration;
|
|
using BCards.Web.Services;
|
|
using BCards.Web.Repositories;
|
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
using Microsoft.AspNetCore.Authentication.Google;
|
|
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
|
|
using Microsoft.AspNetCore.Localization;
|
|
using Microsoft.Extensions.Options;
|
|
using MongoDB.Driver;
|
|
using System.Globalization;
|
|
using Stripe;
|
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
|
using SendGrid;
|
|
using BCards.Web.Middleware;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Add services to the container.
|
|
builder.Services.AddControllersWithViews()
|
|
.AddRazorRuntimeCompilation()
|
|
.AddViewLocalization()
|
|
.AddDataAnnotationsLocalization();
|
|
|
|
// MongoDB Configuration
|
|
builder.Services.Configure<MongoDbSettings>(
|
|
builder.Configuration.GetSection("MongoDb"));
|
|
|
|
builder.Services.AddSingleton<IMongoClient>(serviceProvider =>
|
|
{
|
|
var settings = serviceProvider.GetRequiredService<IOptions<MongoDbSettings>>().Value;
|
|
return new MongoClient(settings.ConnectionString);
|
|
});
|
|
|
|
builder.Services.AddScoped(serviceProvider =>
|
|
{
|
|
var client = serviceProvider.GetRequiredService<IMongoClient>();
|
|
var settings = serviceProvider.GetRequiredService<IOptions<MongoDbSettings>>().Value;
|
|
return client.GetDatabase(settings.DatabaseName);
|
|
});
|
|
|
|
// Stripe Configuration
|
|
builder.Services.Configure<StripeSettings>(
|
|
builder.Configuration.GetSection("Stripe"));
|
|
|
|
// OAuth Configuration
|
|
builder.Services.Configure<GoogleAuthSettings>(
|
|
builder.Configuration.GetSection("Authentication:Google"));
|
|
|
|
builder.Services.Configure<MicrosoftAuthSettings>(
|
|
builder.Configuration.GetSection("Authentication:Microsoft"));
|
|
|
|
// Adicionar configurações
|
|
builder.Services.Configure<ModerationSettings>(
|
|
builder.Configuration.GetSection("Moderation"));
|
|
|
|
// Authentication
|
|
builder.Services.AddAuthentication(options =>
|
|
{
|
|
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
|
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
|
|
})
|
|
.AddCookie(options =>
|
|
{
|
|
options.LoginPath = "/Auth/Login";
|
|
options.LogoutPath = "/Auth/Logout";
|
|
options.ExpireTimeSpan = TimeSpan.FromDays(30);
|
|
options.SlidingExpiration = true;
|
|
})
|
|
.AddGoogle(options =>
|
|
{
|
|
var googleAuth = builder.Configuration.GetSection("Authentication:Google");
|
|
options.ClientId = googleAuth["ClientId"] ?? "";
|
|
options.ClientSecret = googleAuth["ClientSecret"] ?? "";
|
|
})
|
|
.AddMicrosoftAccount(options =>
|
|
{
|
|
var msAuth = builder.Configuration.GetSection("Authentication:Microsoft");
|
|
options.ClientId = msAuth["ClientId"] ?? "";
|
|
options.ClientSecret = msAuth["ClientSecret"] ?? "";
|
|
options.CallbackPath = "/signin-microsoft";
|
|
|
|
// Força seleção de conta a cada login
|
|
options.AuthorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
|
|
options.TokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
|
|
|
|
options.Events = new OAuthEvents
|
|
{
|
|
OnRedirectToAuthorizationEndpoint = context =>
|
|
{
|
|
// 1. Força HTTPS em produção
|
|
if (!builder.Environment.IsDevelopment())
|
|
{
|
|
context.RedirectUri = context.RedirectUri.Replace("http://", "https://");
|
|
}
|
|
|
|
// 2. Adiciona prompt=login para forçar seleção de conta
|
|
var redirectUri = context.RedirectUri;
|
|
if (!redirectUri.Contains("prompt="))
|
|
{
|
|
redirectUri += "&prompt=login";
|
|
}
|
|
|
|
context.Response.Redirect(redirectUri);
|
|
return Task.CompletedTask;
|
|
}
|
|
};
|
|
});
|
|
|
|
// Localization
|
|
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
|
|
|
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
|
{
|
|
var supportedCultures = new[]
|
|
{
|
|
new CultureInfo("pt-BR"),
|
|
new CultureInfo("es-ES")
|
|
};
|
|
|
|
options.DefaultRequestCulture = new RequestCulture("pt-BR");
|
|
options.SupportedCultures = supportedCultures;
|
|
options.SupportedUICultures = supportedCultures;
|
|
});
|
|
|
|
// Register Services
|
|
builder.Services.AddScoped<IUserRepository, UserRepository>();
|
|
builder.Services.AddScoped<IUserPageRepository, UserPageRepository>();
|
|
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
|
|
builder.Services.AddScoped<ISubscriptionRepository, SubscriptionRepository>();
|
|
builder.Services.AddSingleton<IModerationAuthService, ModerationAuthService>();
|
|
//builder.Services.AddScoped<IModerationAuthService, ModerationAuthService>();
|
|
|
|
builder.Services.AddScoped<IUserPageService, UserPageService>();
|
|
builder.Services.AddScoped<IThemeService, ThemeService>();
|
|
builder.Services.AddScoped<ISeoService, SeoService>();
|
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
builder.Services.AddScoped<IPaymentService, PaymentService>();
|
|
builder.Services.AddScoped<ICategoryService, CategoryService>();
|
|
builder.Services.AddScoped<IOpenGraphService, OpenGraphService>();
|
|
builder.Services.AddScoped<IModerationService, ModerationService>();
|
|
builder.Services.AddScoped<IEmailService, EmailService>();
|
|
|
|
// Image Storage Service
|
|
builder.Services.AddScoped<IImageStorageService, GridFSImageStorage>();
|
|
|
|
// Configure upload limits for file uploads
|
|
builder.Services.Configure<FormOptions>(options =>
|
|
{
|
|
options.MultipartBodyLengthLimit = 10 * 1024 * 1024; // 10MB for forms with files
|
|
options.ValueLengthLimit = int.MaxValue;
|
|
options.ValueCountLimit = int.MaxValue;
|
|
options.KeyLengthLimit = int.MaxValue;
|
|
});
|
|
|
|
// 🔥 NOVO: LivePage Services
|
|
builder.Services.AddScoped<ILivePageRepository, LivePageRepository>();
|
|
builder.Services.AddScoped<ILivePageService, LivePageService>();
|
|
|
|
// Add HttpClient for OpenGraphService
|
|
builder.Services.AddHttpClient<OpenGraphService>();
|
|
|
|
// Add SendGrid
|
|
builder.Services.AddSingleton<ISendGridClient>(provider =>
|
|
{
|
|
var apiKey = builder.Configuration["SendGrid:ApiKey"];
|
|
return new SendGridClient(apiKey);
|
|
});
|
|
|
|
// Background Services
|
|
builder.Services.AddHostedService<TrialExpirationService>();
|
|
|
|
// Response Caching
|
|
builder.Services.AddResponseCaching();
|
|
builder.Services.AddMemoryCache();
|
|
|
|
builder.Services.AddRazorPages();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Configure the HTTP request pipeline.
|
|
if (!app.Environment.IsDevelopment())
|
|
{
|
|
app.UseExceptionHandler("/Home/Error");
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
app.UseStaticFiles();
|
|
|
|
app.UseRouting();
|
|
|
|
app.UseRequestLocalization();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
// Add custom middleware
|
|
app.UseMiddleware<BCards.Web.Middleware.PlanLimitationMiddleware>();
|
|
app.UseMiddleware<BCards.Web.Middleware.PageStatusMiddleware>();
|
|
app.UseMiddleware<BCards.Web.Middleware.PreviewTokenMiddleware>();
|
|
app.UseMiddleware<ModerationAuthMiddleware>();
|
|
|
|
app.Use(async (context, next) =>
|
|
{
|
|
Console.WriteLine($"=== REQUEST DEBUG ===");
|
|
Console.WriteLine($"Path: {context.Request.Path}");
|
|
Console.WriteLine($"Query: {context.Request.QueryString}");
|
|
Console.WriteLine($"Method: {context.Request.Method}");
|
|
await next();
|
|
});
|
|
|
|
app.UseResponseCaching();
|
|
|
|
// Rotas específicas primeiro
|
|
app.MapControllerRoute(
|
|
name: "userpage-preview-path",
|
|
pattern: "page/preview/{category}/{slug}",
|
|
defaults: new { controller = "UserPage", action = "Preview" },
|
|
constraints: new { category = @"^[a-zA-Z-]+$", slug = @"^[a-z0-9-]+$" });
|
|
|
|
app.MapControllerRoute(
|
|
name: "userpage-click",
|
|
pattern: "page/click/{pageId}",
|
|
defaults: new { controller = "UserPage", action = "RecordClick" });
|
|
|
|
app.MapControllerRoute(
|
|
name: "moderation",
|
|
pattern: "moderation/{action=Dashboard}/{id?}",
|
|
defaults: new { controller = "Moderation" });
|
|
|
|
// Rota principal que vai pegar ?preview=token
|
|
//app.MapControllerRoute(
|
|
// name: "userpage",
|
|
// pattern: "page/{category}/{slug}",
|
|
// defaults: new { controller = "UserPage", action = "Display" },
|
|
// constraints: new { category = @"^[a-zA-Z-]+$", slug = @"^[a-z0-9-]+$" });
|
|
|
|
// 🔥 NOVA ROTA: LivePageController para páginas otimizadas de SEO
|
|
app.MapControllerRoute(
|
|
name: "livepage",
|
|
pattern: "page/{category}/{slug}",
|
|
defaults: new { controller = "LivePage", action = "Display" },
|
|
constraints: new
|
|
{
|
|
category = @"^[a-zA-Z0-9\-\u00C0-\u017F]+$", // ← Aceita acentos
|
|
slug = @"^[a-z0-9-]+$"
|
|
});
|
|
|
|
// Rota padrão por último
|
|
app.MapControllerRoute(
|
|
name: "default",
|
|
pattern: "{controller=Home}/{action=Index}/{id?}");
|
|
|
|
|
|
// Initialize default data
|
|
using (var scope = app.Services.CreateScope())
|
|
{
|
|
var themeService = scope.ServiceProvider.GetRequiredService<IThemeService>();
|
|
var categoryService = scope.ServiceProvider.GetRequiredService<ICategoryService>();
|
|
|
|
try
|
|
{
|
|
// Initialize themes
|
|
var existingThemes = await themeService.GetAvailableThemesAsync();
|
|
if (!existingThemes.Any())
|
|
{
|
|
await themeService.InitializeDefaultThemesAsync();
|
|
}
|
|
|
|
// Initialize categories
|
|
var existingCategories = await categoryService.GetAllCategoriesAsync();
|
|
if (!existingCategories.Any())
|
|
{
|
|
await categoryService.InitializeDefaultCategoriesAsync();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
|
|
logger.LogError(ex, "Error initializing default data");
|
|
}
|
|
}
|
|
|
|
app.Run();
|
|
|
|
// Make Program accessible for integration tests
|
|
public partial class Program { } |