Initial commit carried over from private repo. This is V2.
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m3s
Build and Push Docker Image / docker (push) Successful in 43s

This commit is contained in:
2025-07-04 21:24:12 +02:00
parent 7715816029
commit 4393977389
96 changed files with 3223 additions and 0 deletions

View File

@ -0,0 +1,313 @@
using MessengerApi.Configuration.Model;
using MessengerApi.Configuration.Model.Persistence;
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.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<MessengerConfiguration>(configuration);
builder.Services.AddSingleton<ILogger>(new Factories.LoggerFactory(configuration).CreateLogger());
builder.Services.AddSingleton<SendEndpointHandler>();
builder.Services.AddSingleton<HousekeepingHandler>();
builder.Services.AddSingleton<UserSetupHandler>();
builder.Services.AddSingleton<IDbContextFactory, DbContextFactory>();
builder.Services.AddScoped<Timing>();
builder.Services.AddScoped<Identity>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<SendEndpointHandler>();
builder.Services.AddScoped<ReceiveEndpointHandler>();
builder.Services.AddScoped<AckEndpointHandler>();
builder.Services.AddScoped<PeekEndpointHandler>();
// Authentication.
builder.Services
.AddAuthentication("Bearer")
.AddScheme<AuthenticationSchemeOptions, CustomBearerAuthenticationHandler>("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, string>(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<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
foreach (var proxy in configuration.Proxies)
{
options.KnownProxies.Add(IPAddress.Parse(proxy));
}
});
var app = builder.Build();
app.UseDeveloperExceptionPage();
// DB Migrations
using (var ctx = app.Services.GetRequiredService<IDbContextFactory>().CreateDbContext())
{
var migrationLogger = app.Services.GetRequiredService<ILogger>();
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<HousekeepingHandler>().RemoveOldMessages();
await Task.Delay(TimeSpan.FromMinutes(1));
}
});
}
// User synchronization
var userSetupHandler = app.Services.GetRequiredService<UserSetupHandler>();
userSetupHandler.UpdateFromFile(new FileInfo(Constants.USERFILE_FILENAME)).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<ILogger>();
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.PayloadLifetimeInSeconds);
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();
}
}
}