Skip to content

Commit d88f52a

Browse files
authored
Merge pull request #70 from PandaTechAM/development
Maintenance mode has been added
2 parents 9f627ec + bbc58ba commit d88f52a

33 files changed

+672
-331
lines changed

Readme.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ This package currently supports:
2727
- **OpenTelemetry**: Metrics, traces, and logs with Prometheus support.
2828
- **Health Checks**: Startup validation and endpoints for monitoring.
2929
- **ValidationHelper**: A collection of regex-based validators for common data formats.
30+
- **Maintenance Mode**: Global switch with three modes (`Disabled`, `EnabledForClients`, `EnabledForAll`); clients = all
31+
routes except `/api/admin/*`.
3032
- Various **Extensions and Utilities**, including enumerable, string, dictionary and queryable extensions.
3133

3234
## Prerequisites
@@ -144,6 +146,7 @@ builder
144146
o.RedisConnectionString = "redis://localhost:6379";
145147
o.ChannelPrefix = "app_name:";
146148
})
149+
.AddMaintenanceMode() // Works only with DistributedCache
147150
.AddDistributedSignalR("redis://localhost:6379","app_name:") // or .AddSignalR()
148151
.AddCors()
149152
.AddHealthChecks();
@@ -153,6 +156,7 @@ var app = builder.Build();
153156

154157
app
155158
.UseRequestLogging()
159+
.UseMaintenanceMode() //(place early)
156160
.UseResponseCrafter()
157161
.UseCors()
158162
.MapMinimalApis()
@@ -633,6 +637,17 @@ Integrate OpenTelemetry for observability, including metrics, traces, and loggin
633637
- OTLP exporter
634638
- EF Core telemetry
635639

640+
## Maintenance Mode
641+
642+
- **Modes**
643+
- `Disabled`: normal operation.
644+
- `EnabledForClients`: only `/api/admin/*` or `/hub/admin/*` allowed
645+
- `EnabledForAll`: everything blocked except `/above-board/*` and `OPTIONS`.
646+
- **Security**: use your own auth (recommended). If you don’t have auth yet, you can pass a shared secret to
647+
`MapMaintenanceEndpoint(basePath, querySecret)`.
648+
649+
> Currently, this feature requires a Pandatech.DistributedCache to work correctly.
650+
636651
## HealthChecks
637652
638653
- **Startup Validation:** `app.EnsureHealthy()` performs a health check at startup and terminates the application if it
@@ -730,6 +745,7 @@ This package includes various extensions and utilities to aid development:
730745
retrieves DefaultTimeZone from `appsettings.json` and sets it as the default time zone.
731746
- **UrlBuilder:** A utility for building URLs with query parameters.
732747
- **Language ISO Code Helper:** Validate, query, and retrieve information about ISO language codes.
748+
- **PhoneUtil class** Utility class for phone number formatting.
733749
734750
### Related NuGet Packages
735751

SharedKernel.Demo/Context/InMemoryContext.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ public class InMemoryContext(DbContextOptions<InMemoryContext> options) : DbCont
77
{
88
public DbSet<OutboxMessage> OutboxMessages { get; set; }
99

10-
protected override void OnModelCreating(ModelBuilder b) =>
10+
protected override void OnModelCreating(ModelBuilder b)
11+
{
1112
b.Entity<OutboxMessage>()
1213
.ToTable("outbox_messages")
1314
.HasKey(x => x.Id);
15+
}
1416
}
1517

1618
public class OutboxMessage

SharedKernel.Demo/LoggingTestEndpoints.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text;
2+
using System.Text.Json;
23
using FluentMinimalApiMapper;
34
using Microsoft.AspNetCore.Mvc;
45

@@ -83,7 +84,7 @@ public void AddRoutes(IEndpointRouteBuilder app)
8384
});
8485

8586
grp.MapGet("/no-content-type",
86-
async (HttpContext ctx) =>
87+
async ctx =>
8788
{
8889
await ctx.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("raw body with no content-type"));
8990
});
@@ -104,23 +105,27 @@ public void AddRoutes(IEndpointRouteBuilder app)
104105
() =>
105106
{
106107
var sb = new StringBuilder();
107-
for (var i = 0; i < 20_000; i++) sb.Append('x');
108+
for (var i = 0; i < 20_000; i++)
109+
{
110+
sb.Append('x');
111+
}
112+
108113
return Results.Text(sb.ToString(), "text/plain", Encoding.UTF8);
109114
});
110115

111116
grp.MapPost("/echo-with-headers",
112-
([FromBody] SharedKernel.Demo.TestTypes payload, HttpResponse res) =>
117+
([FromBody] TestTypes payload, HttpResponse res) =>
113118
{
114119
res.Headers["Custom-Header-Response"] = "CustomValue";
115120
res.ContentType = "application/json; charset=utf-8";
116121
return Results.Json(payload);
117122
});
118123

119124
grp.MapGet("/ping", () => Results.Text("pong", "text/plain"));
120-
125+
121126

122127
grp.MapGet("/invalid-json",
123-
async (HttpContext ctx) =>
128+
async ctx =>
124129
{
125130
ctx.Response.StatusCode = StatusCodes.Status200OK;
126131
ctx.Response.ContentType = "application/json";
@@ -134,27 +139,31 @@ public void AddRoutes(IEndpointRouteBuilder app)
134139
var httpClient = httpClientFactory.CreateClient("RandomApiClient");
135140
httpClient.DefaultRequestHeaders.Add("auth", "hardcoded-auth-value");
136141

137-
var body = new SharedKernel.Demo.TestTypes
142+
var body = new TestTypes
138143
{
139144
AnimalType = AnimalType.Cat,
140145
JustText = "Hello from Get Data",
141146
JustNumber = 100
142147
};
143148

144-
var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(body),
145-
System.Text.Encoding.UTF8,
149+
var content = new StringContent(JsonSerializer.Serialize(body),
150+
Encoding.UTF8,
146151
"application/json");
147152

148153
var response = await httpClient.PostAsync("tests/echo-with-headers?barev=5", content);
149154

150155
if (!response.IsSuccessStatusCode)
156+
{
151157
throw new Exception("Something went wrong");
158+
}
152159

153160
var responseBody = await response.Content.ReadAsStringAsync();
154-
var testTypes = System.Text.Json.JsonSerializer.Deserialize<SharedKernel.Demo.TestTypes>(responseBody);
161+
var testTypes = JsonSerializer.Deserialize<TestTypes>(responseBody);
155162

156163
if (testTypes == null)
164+
{
157165
throw new Exception("Failed to get data from external API");
166+
}
158167

159168
return TypedResults.Ok(testTypes);
160169
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using FluentMinimalApiMapper;
2+
3+
namespace SharedKernel.Demo;
4+
5+
public class MaintenanceTestEndpoints : IEndpoint
6+
{
7+
public void AddRoutes(IEndpointRouteBuilder app)
8+
{
9+
var grp = app.MapGroup("/")
10+
.WithTags("maintenance");
11+
12+
13+
grp.MapGet("/api/admin/v1/test", () => Results.Ok("ok"));
14+
grp.MapGet("/api/admin/v2/test", () => Results.Ok("ok"));
15+
grp.MapGet("/api/integration/v1/test", () => Results.Ok("ok"));
16+
}
17+
}

SharedKernel.Demo/MassTransitExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static WebApplicationBuilder AddRmqHealthCheck(this WebApplicationBuilder
4545
var rmqConnectionString = builder.Configuration.GetConnectionString("RabbitMq")!;
4646
var factory = new ConnectionFactory
4747
{
48-
Uri = new Uri(rmqConnectionString),
48+
Uri = new Uri(rmqConnectionString)
4949
};
5050
return factory.CreateConnectionAsync()
5151
.GetAwaiter()

SharedKernel.Demo/MessageHub.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ public async Task SendMessage(SendMessageRequest message)
1313

1414
public class SendMessageRequest : IHubArgument
1515
{
16-
public required string InvocationId { get; set; }
1716
public required string Message { get; set; }
17+
public required string InvocationId { get; set; }
1818
}

SharedKernel.Demo/Program.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using DistributedCache.Extensions;
22
using FluentMinimalApiMapper;
3-
using FluentValidation;
4-
using MediatR;
53
using Microsoft.AspNetCore.Mvc;
64
using Microsoft.EntityFrameworkCore;
75
using ResponseCrafter.Enums;
@@ -12,6 +10,7 @@
1210
using SharedKernel.Helpers;
1311
using SharedKernel.Logging;
1412
using SharedKernel.Logging.Middleware;
13+
using SharedKernel.Maintenance;
1514
using SharedKernel.OpenApi;
1615
using SharedKernel.Resilience;
1716
using SharedKernel.ValidatorAndMediatR;
@@ -26,16 +25,17 @@
2625
.AddSerilog(LogBackend.ElasticSearch)
2726
.AddResponseCrafter(NamingConvention.ToSnakeCase)
2827
.AddOpenApi()
28+
.AddMaintenanceMode()
2929
.AddOpenTelemetry()
3030
.AddMinimalApis(AssemblyRegistry.ToArray())
3131
.AddControllers(AssemblyRegistry.ToArray())
3232
.AddMediatrWithBehaviors(AssemblyRegistry.ToArray())
3333
.AddResilienceDefaultPipeline()
34-
.AddDistributedSignalR("localhost:6379", "app_name:") // or .AddSignalR()
34+
.AddDistributedSignalR("localhost:6379", "app_name") // or .AddSignalR()
3535
.AddDistributedCache(o =>
3636
{
3737
o.RedisConnectionString = "localhost:6379";
38-
o.ChannelPrefix = "app_name:";
38+
o.ChannelPrefix = "app_name";
3939
})
4040
.AddMassTransit(AssemblyRegistry.ToArray())
4141
.MapDefaultTimeZone()
@@ -66,6 +66,7 @@
6666

6767
app
6868
.UseRequestLogging()
69+
.UseMaintenanceMode()
6970
.UseResponseCrafter()
7071
.UseCors()
7172
.MapMinimalApis()
@@ -78,6 +79,8 @@
7879

7980
app.CreateInMemoryDb();
8081

82+
app.MapMaintenanceEndpoint();
83+
8184

8285
app.MapGet("/outbox-count",
8386
async (InMemoryContext db) =>

SharedKernel.Demo/SharedKernel.Demo.csproj

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,23 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="9.0.0" />
11-
<PackageReference Include="MassTransit.RabbitMQ" Version="8.5.2" />
12-
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8" />
13-
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
10+
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="9.0.0"/>
11+
<PackageReference Include="MassTransit.RabbitMQ" Version="8.5.2"/>
12+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8"/>
13+
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8"/>
1414
</ItemGroup>
1515

1616
<ItemGroup>
17-
<ProjectReference Include="..\src\SharedKernel\SharedKernel.csproj" />
17+
<ProjectReference Include="..\src\SharedKernel\SharedKernel.csproj"/>
1818
</ItemGroup>
1919

2020
<ItemGroup>
21-
<Content Update="appsettings.json">
22-
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
23-
</Content>
24-
<Content Update="appsettings.Development.json">
25-
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
26-
</Content>
21+
<Content Update="appsettings.json">
22+
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
23+
</Content>
24+
<Content Update="appsettings.Development.json">
25+
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
26+
</Content>
2727
</ItemGroup>
2828

2929
</Project>

src/SharedKernel/Extensions/ControllerExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ public static class ControllerExtensions
1010
{
1111
public static WebApplicationBuilder AddControllers(this WebApplicationBuilder builder, Assembly[] assemblies)
1212
{
13-
var mvcBuilder = builder.Services.AddControllers(options => options.Conventions.Add(new ToLowerNamingConvention()));
13+
var mvcBuilder =
14+
builder.Services.AddControllers(options => options.Conventions.Add(new ToLowerNamingConvention()));
1415
foreach (var assembly in assemblies)
1516
{
1617
mvcBuilder.AddApplicationPart(assembly);

src/SharedKernel/Extensions/FusionCacheExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@
3939
// AddBaseFusionCache(builder, instanceName);
4040
// return builder;
4141
// }
42-
// }
42+
// }
43+

0 commit comments

Comments
 (0)