Files
Swarm/DysonNetwork.Zone/Publication/PublicationSiteMiddleware.cs
2025-12-12 00:10:57 +08:00

182 lines
6.6 KiB
C#

using System.Text.Json;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Zone.Customization;
using DysonNetwork.Zone.Pages.Dynamic;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Zone.Publication;
public class PublicationSiteMiddleware(RequestDelegate next)
{
public const string SiteContextKey = "PubSite";
public async Task InvokeAsync(
HttpContext context,
AppDatabase db,
PublicationSiteManager psm,
IRazorViewEngine viewEngine,
ITempDataProvider tempData
)
{
var siteNameValue = context.Request.Headers["X-SiteName"].ToString();
var currentPath = context.Request.Path.Value ?? "";
if (string.IsNullOrEmpty(siteNameValue))
{
await next(context);
return;
}
var site = await db.PublicationSites
.FirstOrDefaultAsync(s => EF.Functions.ILike(s.Slug, siteNameValue)
);
if (site == null)
{
await next(context);
return;
}
context.Items[SiteContextKey] = site;
var page = await db.PublicationPages
.FirstOrDefaultAsync(p => p.SiteId == site.Id && p.Path == currentPath);
if (page != null)
{
switch (page.Type)
{
case PublicationPageType.HtmlPage
when page.Config.TryGetValue("html", out var html) && html is JsonElement content:
if (site.Mode == PublicationSiteMode.FullyManaged)
{
context.Items["PublicationHtmlContent"] = content.ToString();
var layoutedHtml = await RenderViewAsync(
context,
viewEngine,
tempData,
"/Pages/Dynamic/DynamicPage.cshtml",
new DynamicPage { Html = content.ToString() }
);
context.Response.ContentType = "text/html";
await context.Response.WriteAsync(layoutedHtml);
}
else
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync(content.ToString());
}
return;
case PublicationPageType.Redirect
when page.Config.TryGetValue("target", out var tgt) && tgt is JsonElement redirectUrl:
context.Response.Redirect(redirectUrl.ToString());
return;
case PublicationPageType.PostPage:
PostPageFilterConfig? filterConfig = null;
if (page.Config.TryGetValue("filter", out var filter) && filter is JsonElement filterJson)
filterConfig = filterJson.Deserialize<PostPageFilterConfig>(GrpcTypeHelper.SerializerOptions);
PostPageLayoutConfig? layoutConfig = null;
if (page.Config.TryGetValue("layout", out var layout) && layout is JsonElement layoutJson)
layoutConfig = layoutJson.Deserialize<PostPageLayoutConfig>(GrpcTypeHelper.SerializerOptions);
context.Items["PostPage_LayoutConfig"] = layoutConfig;
context.Items["PostPage_FilterConfig"] = filterConfig;
context.Request.Path = "/Posts";
await next(context);
return;
default:
throw new ArgumentOutOfRangeException();
}
}
// If the site is enabled the self-managed mode, try lookup the files then
if (site.Mode == PublicationSiteMode.SelfManaged)
{
var provider = new FileExtensionContentTypeProvider();
var hostedFilePath = psm.GetValidatedFullPath(site.Id, currentPath);
if (File.Exists(hostedFilePath))
{
if (!provider.TryGetContentType(hostedFilePath, out var mimeType))
mimeType = "text/html";
context.Response.ContentType = mimeType;
await context.Response.SendFileAsync(hostedFilePath);
return;
}
if (Directory.Exists(hostedFilePath))
{
var indexPath = Path.Combine(hostedFilePath, "index.html");
if (File.Exists(indexPath))
{
context.Response.ContentType = "text/html";
await context.Response.SendFileAsync(indexPath);
return;
}
}
var hostedNotFoundPath = psm.GetValidatedFullPath(site.Id, "404.html");
if (File.Exists(hostedNotFoundPath))
{
context.Response.ContentType = "text/html";
await context.Response.SendFileAsync(hostedNotFoundPath);
return;
}
}
await next(context);
}
private async Task<string> RenderViewAsync(
HttpContext context,
IRazorViewEngine engine,
ITempDataProvider tempDataProvider,
string viewPath,
object model)
{
var endpointFeature = context.Features.Get<IEndpointFeature>();
var endpoint = endpointFeature?.Endpoint;
var routeData = context.GetRouteData();
var actionContext = new ActionContext(
context,
routeData,
new ActionDescriptor()
);
await using var sw = new StringWriter();
var viewResult = engine.GetView(null, viewPath, true);
if (!viewResult.Success)
throw new FileNotFoundException($"View '{viewPath}' not found.");
var viewData = new ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary())
{
Model = model
};
var tempData = new TempDataDictionary(context, tempDataProvider);
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewData,
tempData,
sw,
new HtmlHelperOptions()
);
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
}
}