Initial commit with inital CMS
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SlpModularCms.Core.Availability;
|
||||
using SlpModularCms.Core.Modules;
|
||||
using SlpModularCms.Modules.Availability.Middleware;
|
||||
using SlpModularCms.Modules.Availability.Services;
|
||||
|
||||
namespace SlpModularCms.Modules.Availability;
|
||||
|
||||
public class AvailabilityModule : IModule
|
||||
{
|
||||
public string Name => "Availability";
|
||||
public string Version => "1.0.0";
|
||||
|
||||
public void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IAvailabilityService, PersistentAvailabilityService>();
|
||||
services.AddScoped<PersistentAvailabilityService>();
|
||||
}
|
||||
|
||||
public void UseModule(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMiddleware<AvailabilityMiddleware>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SlpModularCms.Core.Availability;
|
||||
using SlpModularCms.Modules.Availability.Services;
|
||||
|
||||
namespace SlpModularCms.Modules.Availability.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class AvailabilityController : ControllerBase
|
||||
{
|
||||
private readonly IAvailabilityService _availabilityService;
|
||||
|
||||
public AvailabilityController(IAvailabilityService availabilityService)
|
||||
{
|
||||
_availabilityService = availabilityService;
|
||||
}
|
||||
|
||||
[HttpGet("status")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> GetStatus()
|
||||
{
|
||||
var status = await _availabilityService.IsAvailableAsync();
|
||||
return Ok(new
|
||||
{
|
||||
Status = status.ToString(),
|
||||
CheckedAt = DateTimeOffset.UtcNow,
|
||||
Message = status == AvailabilityStatus.Available
|
||||
? "System is running normally."
|
||||
: "System is in maintenance or unavailable."
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("admin/status")]
|
||||
[Authorize(Policy = "OwnerOnly")]
|
||||
public async Task<IActionResult> UpdateStatus([FromBody] UpdateStatusRequest request)
|
||||
{
|
||||
if (_availabilityService is PersistentAvailabilityService persistentService)
|
||||
{
|
||||
await persistentService.UpdateStatusAsync(
|
||||
request.NewStatus,
|
||||
request.Reason,
|
||||
User.Identity?.Name);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return BadRequest("Status update niet ondersteund door huidige service.");
|
||||
}
|
||||
}
|
||||
|
||||
public record UpdateStatusRequest(
|
||||
AvailabilityStatus NewStatus,
|
||||
string? Reason
|
||||
);
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SlpModularCms.Core.Availability;
|
||||
using SlpModularCms.Core.Exceptions;
|
||||
|
||||
namespace SlpModularCms.Modules.Availability.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Middleware die de beschikbaarheid van het systeem controleert en requests blokkeert indien nodig.
|
||||
/// </summary>
|
||||
public class AvailabilityMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<AvailabilityMiddleware> _logger;
|
||||
|
||||
public AvailabilityMiddleware(RequestDelegate next, ILogger<AvailabilityMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, IAvailabilityService availabilityService)
|
||||
{
|
||||
// Bypass voor status endpoint
|
||||
if (context.Request.Path.StartsWithSegments("/api/availability/status"))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var status = await availabilityService.IsAvailableAsync();
|
||||
|
||||
if (status == AvailabilityStatus.Available)
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check voor Beheerder Bypass (Owner of Administrator)
|
||||
if (IsAdminBypass(context))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogWarning("Request geblokkeerd vanwege systeemstatus: {Status}. Path: {Path}", status, context.Request.Path);
|
||||
|
||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
var response = new ApiErrorResponse(
|
||||
Message: status == AvailabilityStatus.Maintenance
|
||||
? "Het systeem is momenteel in onderhoud. Probeer het later opnieuw."
|
||||
: "De service is tijdelijk niet beschikbaar."
|
||||
);
|
||||
|
||||
await context.Response.WriteAsJsonAsync(response);
|
||||
}
|
||||
|
||||
private bool IsAdminBypass(HttpContext context)
|
||||
{
|
||||
var authHeader = context.Request.Headers.Authorization.ToString();
|
||||
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var tokenString = authHeader.Substring("Bearer ".Length);
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var token = handler.ReadJwtToken(tokenString);
|
||||
|
||||
var roles = token.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value);
|
||||
return roles.Any(r => r == "Owner" || r == "Administrator");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ongeldig token of parsing fout: geen bypass
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SlpModularCms.Core.Availability;
|
||||
using SlpModularCms.Core.Data;
|
||||
using SlpModularCms.Core.Identity.Entities;
|
||||
|
||||
namespace SlpModularCms.Modules.Availability.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Persistente implementatie van de IAvailabilityService met Circuit Breaker.
|
||||
/// </summary>
|
||||
public class PersistentAvailabilityService : IAvailabilityService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly AvailabilityOptions _options;
|
||||
|
||||
// Circuit Breaker state
|
||||
private static DateTimeOffset _lastErrorTime = DateTimeOffset.MinValue;
|
||||
private static AvailabilityStatus _cachedStatus = AvailabilityStatus.Available;
|
||||
|
||||
public PersistentAvailabilityService(ApplicationDbContext context, IOptions<AvailabilityOptions> options)
|
||||
{
|
||||
_context = context;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public async Task<AvailabilityStatus> IsAvailableAsync()
|
||||
{
|
||||
// Check Circuit Breaker
|
||||
if (DateTimeOffset.UtcNow - _lastErrorTime < TimeSpan.FromSeconds(_options.CircuitBreakerSeconds))
|
||||
{
|
||||
return _cachedStatus;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var state = await _context.AvailabilityStates.FirstOrDefaultAsync();
|
||||
|
||||
// Als er nog geen state is, maken we een default aan (Available)
|
||||
if (state == null)
|
||||
{
|
||||
return AvailabilityStatus.Available;
|
||||
}
|
||||
|
||||
_cachedStatus = state.Status;
|
||||
return state.Status;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Database fout: activeer Circuit Breaker
|
||||
_lastErrorTime = DateTimeOffset.UtcNow;
|
||||
_cachedStatus = AvailabilityStatus.Available; // Veilige fallback
|
||||
return _cachedStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update de globale systeemstatus.
|
||||
/// </summary>
|
||||
public async Task UpdateStatusAsync(AvailabilityStatus newStatus, string? reason, string? updatedBy)
|
||||
{
|
||||
var state = await _context.AvailabilityStates.FirstOrDefaultAsync();
|
||||
|
||||
if (state == null)
|
||||
{
|
||||
state = new GlobalAvailabilityState
|
||||
{
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
_context.AvailabilityStates.Add(state);
|
||||
}
|
||||
|
||||
state.Status = newStatus;
|
||||
state.Message = reason;
|
||||
state.LastUpdatedAt = DateTimeOffset.UtcNow;
|
||||
state.UpdatedBy = updatedBy;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Reset cache
|
||||
_cachedStatus = newStatus;
|
||||
_lastErrorTime = DateTimeOffset.MinValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SlpModularCms.Core\SlpModularCms.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user