diff options
Diffstat (limited to 'server/src/Program.cs')
| -rw-r--r-- | server/src/Program.cs | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/server/src/Program.cs b/server/src/Program.cs new file mode 100644 index 0000000..b449117 --- /dev/null +++ b/server/src/Program.cs @@ -0,0 +1,261 @@ +global using System; +global using System.Linq; +global using System.IO; +global using System.Net.Mail; +global using System.Net; +global using System.Threading; +global using System.Threading.Tasks; +global using System.Collections.Generic; +global using System.Runtime.Serialization; +global using System.ComponentModel.DataAnnotations.Schema; +global using System.Security.Claims; +global using System.Text.Json; +global using System.Text.Json.Serialization; +global using IOL.GreatOffice.Api.Data.Database; +global using IOL.GreatOffice.Api.Data.Exceptions; +global using IOL.GreatOffice.Api.Data.Dtos; +global using IOL.GreatOffice.Api.Data.Enums; +global using IOL.GreatOffice.Api.Data.Models; +global using IOL.GreatOffice.Api.Data.Results; +global using IOL.Helpers; +global using Microsoft.OpenApi.Models; +global using Microsoft.AspNetCore.Authentication.Cookies; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.DataProtection; +global using Microsoft.AspNetCore.Hosting; +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Authentication; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Serilog; +global using IOL.GreatOffice.Api.Data; +global using IOL.GreatOffice.Api.Data.Static; +global using IOL.GreatOffice.Api.Services; +global using IOL.GreatOffice.Api.Utilities; +using System.Diagnostics; +using System.Reflection; +using IOL.GreatOffice.Api.Endpoints.V1; +using IOL.GreatOffice.Api.Jobs; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.Mvc.Versioning; +using Quartz; + +namespace IOL.GreatOffice.Api; + +public static class Program +{ + public static WebApplicationBuilder CreateAppBuilder(string[] args) { + var builder = WebApplication.CreateBuilder(args); + + var seqUrl = builder.Configuration.GetValue<string>(AppEnvironmentVariables.SEQ_API_URL); + var seqKey = builder.Configuration.GetValue<string>(AppEnvironmentVariables.SEQ_API_KEY); + var logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .ReadFrom.Configuration(builder.Configuration) + .WriteTo.Console(); + + if (!builder.Environment.IsDevelopment() && seqUrl.HasValue() && seqKey.HasValue()) { + logger.WriteTo.Seq(seqUrl, apiKey: seqKey); + } + + Log.Logger = logger.CreateLogger(); + Log.Information("Starting web host" + + JsonSerializer.Serialize(new { + DateTime.UtcNow, + PID = Environment.ProcessId, + DB_HOST = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_HOST), + DB_PORT = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_PORT), + DB_USER = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_USER), + DB_NAME = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_NAME), + DB_PASS = builder.Configuration.GetValue<string>(AppEnvironmentVariables.DB_PASSWORD).Obfuscate() ?? "!!!Empty!!!", + QUARTZ_DB_HOST = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_HOST), + QUARTZ_DB_PORT = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_PORT), + QUARTZ_DB_USER = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_USER), + QUARTZ_DB_NAME = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_NAME), + QUARTZ_DB_PASS = builder.Configuration.GetValue<string>(AppEnvironmentVariables.QUARTZ_DB_PASSWORD).Obfuscate() + ?? "!!!Empty!!!", + }, + new JsonSerializerOptions() { + WriteIndented = true + })); + + builder.Host.UseSerilog(Log.Logger); + builder.WebHost.ConfigureKestrel(kestrel => { + kestrel.AddServerHeader = false; + }); + + if (builder.Environment.IsDevelopment()) { + builder.Services.AddCors(); + } + + if (builder.Environment.IsProduction()) { + builder.Services.Configure<ForwardedHeadersOptions>(options => { + options.ForwardedHeaders = ForwardedHeaders.XForwardedProto; + }); + } + + builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(AppPaths.DataProtectionKeys.HostPath)); + + builder.Services.Configure(JsonSettings.Default); + builder.Services.AddQuartz(options => { + options.UsePersistentStore(o => { + o.UsePostgres(builder.Configuration.GetQuartzDatabaseConnectionString()); + o.UseSerializer<QuartzJsonSerializer>(); + }); + options.UseMicrosoftDependencyInjectionJobFactory(); + options.RegisterJobs(); + }); + builder.Services.AddQuartzHostedService(options => { + options.WaitForJobsToComplete = true; + }); + + builder.Services.AddAuthentication(options => { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }) + .AddCookie(options => { + options.Cookie.Name = "go_session"; + options.Cookie.SameSite = SameSiteMode.Strict; + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = builder.Environment.IsDevelopment() ? CookieSecurePolicy.SameAsRequest : CookieSecurePolicy.Always; + options.Cookie.IsEssential = true; + options.SlidingExpiration = true; + options.Events.OnRedirectToAccessDenied = + options.Events.OnRedirectToLogin = c => { + c.Response.StatusCode = StatusCodes.Status401Unauthorized; + return Task.FromResult<object>(null); + }; + }) + .AddGitHub(options => { + options.ClientSecret = builder.Configuration.GetValue<string>(AppEnvironmentVariables.GITHUB_CLIENT_SECRET); + options.ClientId = builder.Configuration.GetValue<string>(AppEnvironmentVariables.GITHUB_CLIENT_ID); + options.SaveTokens = true; + options.CorrelationCookie.Name = "gh_correlation"; + options.CorrelationCookie.SameSite = SameSiteMode.Lax; + options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always; + options.CorrelationCookie.HttpOnly = true; + options.Events.OnCreatingTicket = context => GithubAuthenticationHelpers.HandleGithubTicketCreation(context, builder.Configuration); + }) + .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(AppConstants.BASIC_AUTH_SCHEME, default); + + + builder.Services.AddDbContext<AppDbContext>(options => { + options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(), + npgsqlDbContextOptionsBuilder => { + npgsqlDbContextOptionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + npgsqlDbContextOptionsBuilder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), default); + }) + .UseSnakeCaseNamingConvention(); + if (builder.Environment.IsDevelopment()) { + options.EnableSensitiveDataLogging(); + } + }); + + builder.Services.AddApiVersioning(options => { + options.ApiVersionReader = new UrlSegmentApiVersionReader(); + options.ReportApiVersions = true; + options.AssumeDefaultVersionWhenUnspecified = false; + }); + builder.Services.AddVersionedApiExplorer(options => { + options.SubstituteApiVersionInUrl = true; + }); + builder.Services.AddSwaggerGen(options => { + options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, Assembly.GetExecutingAssembly().GetName().Name + ".xml")); + options.UseApiEndpoints(); + options.OperationFilter<SwaggerDefaultValues>(); + options.SwaggerDoc(ApiSpecV1.Document.VersionName, ApiSpecV1.Document.OpenApiInfo); + options.AddSecurityDefinition("Basic", + new OpenApiSecurityScheme { + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + Scheme = "Basic", + BearerFormat = "Basic", + In = ParameterLocation.Header, + Description = + "Enter your token in the text input below.\r\n\r\nExample: \"Basic 12345abcdef\"", + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Type = ReferenceType.SecurityScheme, + Id = "Basic" + } + }, + Array.Empty<string>() + } + }); + }); + + builder.Services.AddScoped<MailService>(); + builder.Services.AddScoped<ForgotPasswordService>(); + builder.Services.AddScoped<UserService>(); + builder.Services.AddLogging(); + builder.Services.AddHttpClient(); + builder.Services + .AddControllers() + .AddJsonOptions(JsonSettings.Default); + + + return builder; + } + + public static WebApplication CreateWebApplication(WebApplicationBuilder builder) { + var app = builder.Build(); + + if (app.Environment.IsDevelopment()) { + app.UseDeveloperExceptionPage(); + app.UseCors(cors => { + cors.AllowAnyMethod(); + cors.AllowAnyHeader(); + cors.WithOrigins("http://localhost:3000", "http://localhost:3002", "http://localhost:3001"); + cors.AllowCredentials(); + }); + } + + if (app.Environment.IsProduction()) { + app.UseForwardedHeaders(); + } + + app.UseDefaultFiles() + .UseStaticFiles() + .UseRouting() + .UseSerilogRequestLogging() + .UseStatusCodePages() + .UseAuthentication() + .UseAuthorization() + .UseSwagger() + .UseSwaggerUI(options => { + options.SwaggerEndpoint(ApiSpecV1.Document.SwaggerPath, ApiSpecV1.Document.VersionName); + options.DocumentTitle = AppConstants.API_NAME; + }) + .UseEndpoints(endpoints => { + endpoints.MapControllers(); + }); + return app; + } + + public static int Main(string[] args) { + try { + CreateWebApplication(CreateAppBuilder(args)).Run(); + return 0; + } catch (Exception ex) { + // This is subject to change in future .net versions, see https://github.com/dotnet/runtime/issues/60600. + if (ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) { + throw; + } + + Log.Fatal(ex, "Unhandled exception"); + return 1; + } finally { + Log.Information("Shut down complete, flusing logs..."); + Log.CloseAndFlush(); + } + } +} |
