using MessengerApi.Configuration.Model; using MessengerApi.Configuration.Model.Persistence; using MessengerApi.Configuration.Model.Persistence.Base; using MessengerApi.Configuration.Sources.Environment; using MessengerApi.Contracts.Factories; using MessengerApi.Contracts.Models.Scoped; using MessengerApi.Db; using MessengerApi.Factories; using MessengerApi.Handlers; using MessengerApi.Handlers.Endpoint; using MessengerApi.Models.Http; using MessengerApi.Models.Scoped; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Net; using System.Runtime.CompilerServices; using System.Threading.RateLimiting; namespace MessengerApi.Api { public class Program { public static void Main(string[] args) { MessengerConfiguration configuration = null; try { configuration = new MessengerConfiguration(new EnvironmentConfigurationSource()); } catch (Exception ex) { Console.WriteLine("Can't load settings.", ex); throw; } var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddEnvironmentVariables(); builder.Services.AddMemoryCache(); builder.Services.AddSingleton(configuration); builder.Services.AddSingleton(configuration.PersistenceConfiguration); builder.Services.AddSingleton(new Factories.LoggerFactory(configuration).CreateLogger()); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Authentication. builder.Services .AddAuthentication("Bearer") .AddScheme("Bearer", null); // CORS. builder.Services .AddCors(opt => opt.AddPolicy("originpolicy", builder => { builder .WithOrigins(configuration.Origins.ToArray()) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); })); // Ratelimiting builder.Services.AddRateLimiter(options => { options.GlobalLimiter = PartitionedRateLimiter.Create(httpContext => { var key = httpContext.Request.Headers["Authorization"].FirstOrDefault() ?? "anonymous"; return RateLimitPartition.GetFixedWindowLimiter(key, _ => new FixedWindowRateLimiterOptions { PermitLimit = configuration.RateLimitPerMinute, Window = TimeSpan.FromMinutes(1), QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 0 }); }); options.RejectionStatusCode = 429; }); // Proxy registration to forward real client IPs. builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; foreach (var proxy in configuration.Proxies) { options.KnownProxies.Add(IPAddress.Parse(proxy)); } }); var app = builder.Build(); // DB Migrations using (var ctx = app.Services.GetRequiredService().CreateDbContext()) { var migrationLogger = app.Services.GetRequiredService(); try { if (ctx.Database.GetPendingMigrations().Any()) { migrationLogger.Info("Applying migrations."); ctx.Database.Migrate(); } else { migrationLogger.Info("No migrations pending."); } } catch (Exception ex) { migrationLogger.Error("Can't run migrations successfully.", ex); throw; } } // Housekeeping. if (configuration.HousekeepingEnabled) { _ = Task.Run(async () => { while (true) { await app.Services.GetService().RemoveOldMessages(); await Task.Delay(TimeSpan.FromMinutes(1)); } }); } // User synchronization if (configuration.UsersConfig != null) { app.Services.GetRequiredService().Info($"Running userconfig synchronization for {configuration.UsersConfig.FullName}."); var handler = app.Services.GetRequiredService(); handler.UpdateFromFile(configuration.UsersConfig).GetAwaiter().GetResult(); } app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseCors("originpolicy"); app.UseForwardedHeaders(); // Ray id logging. app.Use(async (context, next) => { var stamp = DateTime.UtcNow; var logger = context.RequestServices.GetRequiredService(); var ipa = context?.Connection?.RemoteIpAddress?.ToString() ?? "unknown"; var uid = context?.User?.Identity?.Name ?? "unknown"; var una = context?.User?.Claims?.SingleOrDefault(x => x.Type == "UserName")?.Value ?? "unknown"; var rid = context?.TraceIdentifier ?? "unknown"; var endpoint = context?.GetEndpoint()?.DisplayName ?? "unknown"; logger.Info($"{endpoint} call {rid}; ip {ipa}; u {una}/{uid}"); await next(); }); app.UseRateLimiter(); // Endpoint registration. app.MapPost("/send", async ( ILogger logger, IUnitOfWork unitOfWork, SendEndpointHandler handler, [FromBody] SendRequest request) => { try { var response = await handler.SendMessage(request.ToUserId, request.Payload, request.PayloadType, request.TimeToLiveInSeconds); await unitOfWork.SaveChanges(); return Results.Json(response.Id); } catch (Exception ex) { logger.Error("Can't send.", ex); return Results.InternalServerError(); } }); app.MapGet("/receive", async ( ILogger logger, IUnitOfWork unitOfWork, ReceiveEndpointHandler handler) => { try { var messages = await handler.ReceiveMessages(); if (messages?.Any() != true) { return Results.NoContent(); } else { await unitOfWork.SaveChanges(); return Results.Json(new { Messages = messages.Select(x => new { Id = x.Id, TimestampUtc = x.CreatedUtc, Payload = x.Payload, PayloadType = x.PayloadType, Sender = x.FromId }) }); } } catch (Exception ex) { logger.Error("Can't send.", ex); return Results.InternalServerError(); } }); app.MapPost("/ack", async ( ILogger logger, IUnitOfWork unitOfWork, AckEndpointHandler handler, AckRequest request) => { try { await handler.AckMessage(request.MessageId); await unitOfWork.SaveChanges(); return Results.Ok(); } catch (Exception ex) { logger.Error("Can't send.", ex); return Results.InternalServerError(); } }); app.MapGet("/yellowpages", ( ILogger logger, IUnitOfWork unitOfWork, Identity identity) => { try { var routes = unitOfWork.UserRoutes.GetByFrom(identity.User).ToList(); return Results.Json(new { Users = routes.Select(x => new { Id = x.To.Id, Name = x.To.Name }) }); } catch (Exception ex) { logger.Error("Can't yellowpages.", ex); return Results.InternalServerError(); } }); app.MapGet("/peek", async ( ILogger logger, PeekEndpointHandler handler) => { try { var pending = await handler.Peek(); return Results.Json(pending); } catch (Exception ex) { logger.Error("Can't peek.", ex); return Results.InternalServerError(); } }); app.MapGet("/verify", ( ILogger logger, IUnitOfWork unitOfWork, Guid messageId) => { try { var message = unitOfWork.Messages.GetById(messageId); return Results.Json(new { message.IsDelivered, message.IsAcknowledged }); } catch (Exception ex) { logger.Error("Can't verify.", ex); return Results.InternalServerError(); } }); app.Run(); } } }