using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using LittleShop.Data; using LittleShop.Services; using FluentValidation; using Serilog; var builder = WebApplication.CreateBuilder(args); // Configure Serilog Log.Logger = new LoggerConfiguration() .WriteTo.Console() .WriteTo.File("logs/littleshop.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); builder.Host.UseSerilog(); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddControllersWithViews(); // Add MVC for Admin Panel // Database if (builder.Environment.EnvironmentName == "Testing") { builder.Services.AddDbContext(options => options.UseInMemoryDatabase("InMemoryDbForTesting")); } else { builder.Services.AddDbContext(options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); } // Authentication - Cookie for Admin Panel, JWT for API var jwtKey = builder.Configuration["Jwt:Key"] ?? "ThisIsASuperSecretKeyForJWTAuthenticationThatIsDefinitelyLongerThan32Characters!"; var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "LittleShop"; var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "LittleShop"; builder.Services.AddAuthentication("Cookies") .AddCookie("Cookies", options => { options.LoginPath = "/Admin/Account/Login"; options.LogoutPath = "/Admin/Account/Logout"; options.AccessDeniedPath = "/Admin/Account/AccessDenied"; }) .AddJwtBearer("Bearer", options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtIssuer, ValidAudience = jwtAudience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)) }; }); builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireAuthenticatedUser() .RequireRole("Admin") .AddAuthenticationSchemes("Cookies", "Bearer")); // Support both cookie and JWT options.AddPolicy("ApiAccess", policy => policy.RequireAuthenticatedUser() .AddAuthenticationSchemes("Bearer")); // JWT only for API access }); // Services builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // BTCPay removed - using SilverPAY only // SilverPay service - using SilverPAY with optional mock for testing if (builder.Configuration.GetValue("SilverPay:UseMockService", false)) { builder.Services.AddSingleton(); Console.WriteLine("⚠️ Using MOCK SilverPAY service - payments won't be real!"); } else { builder.Services.AddHttpClient(); } builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); // SignalR builder.Services.AddSignalR(); // Health Checks builder.Services.AddHealthChecks() .AddDbContextCheck("database") .AddCheck("self", () => Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Healthy("Application is healthy")); // Temporarily disabled to use standalone TeleBot with customer orders fix // builder.Services.AddHostedService(); // AutoMapper builder.Services.AddAutoMapper(typeof(Program)); // FluentValidation builder.Services.AddValidatorsFromAssemblyContaining(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "LittleShop API", Version = "v1", Description = "A basic online sales system backend with multi-cryptocurrency payment support", Contact = new Microsoft.OpenApi.Models.OpenApiContact { Name = "LittleShop Support" } }); // Add JWT authentication to Swagger c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme { Description = "JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below.", Name = "Authorization", In = Microsoft.OpenApi.Models.ParameterLocation.Header, Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey, Scheme = "Bearer" }); c.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement { { new Microsoft.OpenApi.Models.OpenApiSecurityScheme { Reference = new Microsoft.OpenApi.Models.OpenApiReference { Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty() } }); }); // CORS - Configure for both development and production builder.Services.AddCors(options => { // Development CORS policy - configured from appsettings options.AddPolicy("DevelopmentCors", corsBuilder => { var allowedOrigins = builder.Configuration.GetSection("CORS:AllowedOrigins").Get() ?? new[] { "http://localhost:3000", "http://localhost:5173", "http://localhost:5000" }; corsBuilder.WithOrigins(allowedOrigins) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); // Important for cookie authentication }); // Production CORS policy - strict security options.AddPolicy("ProductionCors", corsBuilder => { var allowedOrigins = builder.Configuration.GetSection("CORS:AllowedOrigins").Get() ?? new[] { "https://littleshop.silverlabs.uk" }; corsBuilder.WithOrigins(allowedOrigins) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); // API-specific CORS policy (no credentials for public API) options.AddPolicy("ApiCors", corsBuilder => { // Public API should have more restricted CORS corsBuilder.WithOrigins("https://littleshop.silverlabs.uk", "https://pay.silverlabs.uk") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); var app = builder.Build(); // Configure the HTTP request pipeline. // Add CORS early in the pipeline - before authentication if (app.Environment.IsDevelopment()) { app.UseCors("DevelopmentCors"); app.UseSwagger(); app.UseSwaggerUI(); } else { // Use production CORS policy in production environment app.UseCors("ProductionCors"); } // Add error handling middleware for production if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); // Use HSTS for production security } app.UseStaticFiles(); // Enable serving static files app.UseAuthentication(); app.UseAuthorization(); // Configure routing app.MapControllerRoute( name: "admin", pattern: "Admin/{controller=Dashboard}/{action=Index}/{id?}", defaults: new { area = "Admin" } ); app.MapControllerRoute( name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapControllers(); // API routes // Map SignalR hub app.MapHub("/activityHub"); // Health check endpoint app.MapHealthChecks("/health"); // Apply database migrations and seed data using (var scope = app.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService(); // Ensure database is created (temporary while fixing migrations) context.Database.EnsureCreated(); // Seed default admin user var authService = scope.ServiceProvider.GetRequiredService(); await authService.SeedDefaultUserAsync(); // Seed sample data var dataSeeder = scope.ServiceProvider.GetRequiredService(); await dataSeeder.SeedSampleDataAsync(); } Log.Information("LittleShop API starting up..."); app.Run(); // Make Program accessible to test project public partial class Program { }