Initial commit carried over from private repo. This is V2.
This commit is contained in:
313
code/MessengerApi/Program.cs
Normal file
313
code/MessengerApi/Program.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user