diff --git a/Api.SeaHavenIndustries/Middleware/LegacyDeprecationMiddleware.cs b/Api.SeaHavenIndustries/Middleware/LegacyDeprecationMiddleware.cs
new file mode 100644
index 0000000..0e2ccb6
--- /dev/null
+++ b/Api.SeaHavenIndustries/Middleware/LegacyDeprecationMiddleware.cs
@@ -0,0 +1,57 @@
+using Api.SeaHavenIndustries.Options;
+using Microsoft.Extensions.Options;
+
+namespace Api.SeaHavenIndustries.Middleware
+{
+ /// Adds Sunset/Deprecation headers to legacy WorkOrder REST routes.
+ public class LegacyDeprecationMiddleware
+ {
+ private static readonly HashSet LegacyPathSegments = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "GetWorkOrderList",
+ "AddWorkorder",
+ "EditWorkorder",
+ "ChangeStatus",
+ "ChangeAssignment",
+ "DeleteWorkorder",
+ "GetWorkorderById"
+ };
+
+ private readonly RequestDelegate _next;
+ private readonly LegacyEndpointOptions _options;
+
+ public LegacyDeprecationMiddleware(RequestDelegate next, IOptions options)
+ {
+ _next = next;
+ _options = options.Value;
+ }
+
+ public async Task InvokeAsync(HttpContext context)
+ {
+ if (_options.DeprecationEnabled)
+ {
+ var path = context.Request.Path.Value ?? "";
+ var isLegacyWo = (path.Contains("/WorkOrder", StringComparison.OrdinalIgnoreCase)
+ || path.Contains("/workorders", StringComparison.OrdinalIgnoreCase))
+ && LegacyPathSegments.Any(seg => path.Contains(seg, StringComparison.OrdinalIgnoreCase));
+
+ if (isLegacyWo)
+ {
+ context.Response.OnStarting(() =>
+ {
+ context.Response.Headers["Deprecation"] = "true";
+ if (!string.IsNullOrWhiteSpace(_options.SunsetDate)
+ && DateTimeOffset.TryParse(_options.SunsetDate, out var sunset))
+ {
+ context.Response.Headers["Sunset"] = sunset.UtcDateTime.ToString("r");
+ }
+ context.Response.Headers["Link"] = "; rel=\"successor-version\"";
+ return Task.CompletedTask;
+ });
+ }
+ }
+
+ await _next(context);
+ }
+ }
+}
diff --git a/Api.SeaHavenIndustries/Program.cs b/Api.SeaHavenIndustries/Program.cs
index e897f24..c85271b 100644
--- a/Api.SeaHavenIndustries/Program.cs
+++ b/Api.SeaHavenIndustries/Program.cs
@@ -1,5 +1,6 @@
-using Api.SeaHavenIndustries.Helper;
+using Api.SeaHavenIndustries.Helper;
using Api.SeaHavenIndustries.HostedServices;
+using Api.SeaHavenIndustries.Middleware;
using Api.SeaHavenIndustries.Options;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authentication.JwtBearer;
@@ -151,6 +152,7 @@ if (!app.Environment.IsDevelopment())
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors();
+app.UseMiddleware();
app.UseAuthentication();
app.UseAuthorization();
@@ -213,4 +215,3 @@ app.MapControllers();
//}
app.Run();
-
diff --git a/SeaHavenIndustries/Data/Services/WorkorderService.cs b/SeaHavenIndustries/Data/Services/WorkorderService.cs
index f616377..2e18cf2 100644
--- a/SeaHavenIndustries/Data/Services/WorkorderService.cs
+++ b/SeaHavenIndustries/Data/Services/WorkorderService.cs
@@ -22,14 +22,22 @@ namespace SeaHavenIndustries.Data.Services
private UserManager _userManager;
private UploadFileHp _uploadFileHelper;
private readonly SendMessage _sendemail;
+ private readonly BlazorWorkOrderSunsetGuard _sunsetGuard;
- public WorkorderService(SendMessage sendemail, ApplicationDbContext context, IHttpContextAccessor httpContextAccessor, UserManager userManager, UploadFileHp uploadFileHelper)
+ public WorkorderService(
+ SendMessage sendemail,
+ ApplicationDbContext context,
+ IHttpContextAccessor httpContextAccessor,
+ UserManager userManager,
+ UploadFileHp uploadFileHelper,
+ BlazorWorkOrderSunsetGuard sunsetGuard)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
_userManager = userManager;
_uploadFileHelper = uploadFileHelper;
_sendemail = sendemail;
+ _sunsetGuard = sunsetGuard;
}
public async Task> Getworkorder()
{
@@ -178,6 +186,7 @@ namespace SeaHavenIndustries.Data.Services
}
public async Task Addworkorder(WorkOrder model, List files)
{
+ _sunsetGuard.ThrowIfMutationsBlocked();
try
{
_context.ChangeTracker.Clear();
@@ -425,6 +434,7 @@ namespace SeaHavenIndustries.Data.Services
}
public async Task Deleteworkorder(int id)
{
+ _sunsetGuard.ThrowIfMutationsBlocked();
_context.ChangeTracker.Clear();
try
diff --git a/SeaHavenIndustries/Helper/BlazorWorkOrderSunsetGuard.cs b/SeaHavenIndustries/Helper/BlazorWorkOrderSunsetGuard.cs
new file mode 100644
index 0000000..5c7bbf8
--- /dev/null
+++ b/SeaHavenIndustries/Helper/BlazorWorkOrderSunsetGuard.cs
@@ -0,0 +1,26 @@
+using Microsoft.Extensions.Options;
+using SeaHavenIndustries.Options;
+
+namespace SeaHavenIndustries.Helper
+{
+ public class BlazorWorkOrderSunsetGuard
+ {
+ private readonly BlazorWorkOrderSunsetOptions _options;
+
+ public BlazorWorkOrderSunsetGuard(IOptions options)
+ {
+ _options = options.Value;
+ }
+
+ public bool IsSunsetEnabled => _options.Enabled;
+
+ public string ShocBaseUrl => _options.ShocBaseUrl.TrimEnd('/');
+
+ public void ThrowIfMutationsBlocked()
+ {
+ if (_options.Enabled)
+ throw new InvalidOperationException(
+ "Work Orders module has moved to SHOC. Editing via Blazor is disabled.");
+ }
+ }
+}
diff --git a/SeaHavenIndustries/Options/BlazorWorkOrderSunsetOptions.cs b/SeaHavenIndustries/Options/BlazorWorkOrderSunsetOptions.cs
new file mode 100644
index 0000000..e368d9f
--- /dev/null
+++ b/SeaHavenIndustries/Options/BlazorWorkOrderSunsetOptions.cs
@@ -0,0 +1,11 @@
+namespace SeaHavenIndustries.Options
+{
+ public class BlazorWorkOrderSunsetOptions
+ {
+ public const string SectionName = "BlazorWorkOrderSunset";
+
+ public bool Enabled { get; set; }
+
+ public string ShocBaseUrl { get; set; } = "https://shoc.seahaven.com";
+ }
+}
diff --git a/SeaHavenIndustries/Program.cs b/SeaHavenIndustries/Program.cs
index 5450abf..48b76c3 100644
--- a/SeaHavenIndustries/Program.cs
+++ b/SeaHavenIndustries/Program.cs
@@ -77,6 +77,9 @@ builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+builder.Services.Configure(
+ builder.Configuration.GetSection(SeaHavenIndustries.Options.BlazorWorkOrderSunsetOptions.SectionName));
+builder.Services.AddSingleton();
builder.Services.AddSession(options =>
{