using eprintServer.Data; using Microsoft.EntityFrameworkCore; using System.Text.Json.Serialization; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using Microsoft.OpenApi.Models; using eprintServer.Services; using eprintServer.Interfaces; using eprintServer.Data.Seed; using Microsoft.AspNetCore.HttpOverrides; using StackExchange.Redis; using Quartz; using eprintServer.Enums; using QuestPDF.Infrastructure; using Npgsql; using FirebaseAdmin; using Google.Apis.Auth.OAuth2; using Microsoft.AspNetCore.StaticFiles; using Amazon.S3; var builder = WebApplication.CreateBuilder(args); builder.WebHost.ConfigureKestrel(options => { // Example: 500 MB limit (Value is in bytes) options.Limits.MaxRequestBodySize = 524288000; }); AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); QuestPDF.Settings.License = LicenseType.Community; var s3Section = builder.Configuration.GetSection("S3Config"); builder.Services.AddSingleton(sp => { var config = new AmazonS3Config { ServiceURL = s3Section["ServiceUrl"], ForcePathStyle = true // Required for many S3-compatible providers like Hetzner }; return new AmazonS3Client(s3Section["AccessKey"], s3Section["SecretKey"], config); }); // --- 1. CONFIGURE NPGSQL DATA SOURCE --- var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString); dataSourceBuilder.EnableDynamicJson(); var dataSource = dataSourceBuilder.Build(); // --- 2. FIREBASE INITIALIZATION --- var firebaseKeyPath = Path.Combine(Directory.GetCurrentDirectory(), "firebase-auth.json"); if (File.Exists(firebaseKeyPath)) { FirebaseApp.Create(new AppOptions() { Credential = GoogleCredential.FromFile(firebaseKeyPath) }); } // --- 3. SERVICES REGISTRATION --- 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.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(); // Notification Services 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.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddQuartz(q => { // Important: Use the DI Job Factory so StatsService/EmailService can be injected q.UseMicrosoftDependencyInjectionJobFactory(); // Helper to register report jobs void RegisterReportJob(string key, string cron, ReportDateEum type) { var jobKey = new JobKey(key); q.AddJob(opts => opts .WithIdentity(jobKey) // This allows the Job to know which report to generate .UsingJobData("ReportType", type.ToString())); q.AddTrigger(opts => opts .ForJob(jobKey) .WithIdentity(key + "-trigger") .WithCronSchedule(cron)); } // Schedules (Using Standard Cron format: Seconds Minutes Hours DayOfMonth Month DayOfWeek) RegisterReportJob("DailyReport", "0 0 20 * * ?", ReportDateEum.DAILY); // 8 PM Daily RegisterReportJob("WeeklyReport", "0 0 21 ? * SAT", ReportDateEum.WEEKLY); // 9 PM Saturday RegisterReportJob("MonthlyReport", "0 0 22 L * ?", ReportDateEum.MONTHLY); // 10 PM Last day of month RegisterReportJob("YearlyReport", "0 0 23 31 12 ?", ReportDateEum.MONTHLY); // 11 PM Dec 31 }); // This replaces AddQuartzServer if that method isn't found builder.Services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; }); // REDIS CONFIGURATION builder.Services.AddSingleton(sp => { var redisConnectionString = builder.Configuration.GetConnectionString("Redis"); if (string.IsNullOrWhiteSpace(redisConnectionString)) throw new Exception("Redis connection string is missing!"); var options = ConfigurationOptions.Parse(redisConnectionString); options.AbortOnConnectFail = false; options.ConnectRetry = 5; options.KeepAlive = 60; return ConnectionMultiplexer.Connect(options); }); // DB CONTEXT builder.Services.AddDbContext(options => { options.UseNpgsql(dataSource); }); builder.Services.AddCors(options => { options.AddPolicy("CorsPolicy", policy => { policy.WithOrigins( "http://localhost:4200", "https://localhost:4200", "http://localhost:3000", "https://localhost:3000", "http://localhost:3001", "https://localhost:3001", "https://print.camel-soft.com", "https://printdashboardadmin.vercel.app", "https://print-sigma-taupe.vercel.app", "https://printeprojectfrontend.vercel.app", "https://server.etbaaonline.com", "https://etbaaonline.com", "https://dashboard.etbaaonline.com", "null" ) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)) }; 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("/notificationHub")) { context.Token = accessToken; } return Task.CompletedTask; } }; }); builder.Services.AddAuthorization(); builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Name = "Authorization", Type = SecuritySchemeType.Http, Scheme = "Bearer", BearerFormat = "JWT", In = ParameterLocation.Header, Description = "JWT Authorization header using the Bearer scheme." }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty() } }); }); var app = builder.Build(); // --- DATABASE AUTO-MIGRATION LOGIC --- using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; try { var context = services.GetRequiredService(); // This will apply any pending migrations and create tables (Orders, OrderItems, etc.) await context.Database.MigrateAsync(); } catch (Exception ex) { var logger = services.GetRequiredService>(); logger.LogError(ex, "An error occurred while migrating the database."); } } await DbInitializer.InitializeDbAsync(app); app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); app.UseSwagger(); app.UseSwaggerUI(); app.UseHttpsRedirection(); app.UseCors("CorsPolicy"); app.Use((context, next) => { context.Response.Headers.Append("Cross-Origin-Embedder-Policy", "require-corp"); context.Response.Headers.Append("Cross-Origin-Resource-Policy", "cross-origin"); context.Response.Headers.Append("Cross-Origin-Opener-Policy", "same-origin"); return next(); }); app.UseAuthentication(); app.UseAuthorization(); var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".glb"] = "model/gltf-binary"; provider.Mappings[".gltf"] = "model/gltf+json"; app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider }); //app.UseStaticFiles(); app.MapControllers(); app.Run();