🗑️ Remove built-in frontend serving code

This commit is contained in:
2025-09-19 00:14:37 +08:00
parent 1e374a73c7
commit 634958ffc5
25 changed files with 11 additions and 352 deletions

View File

@@ -1,24 +0,0 @@
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.PageData;
namespace DysonNetwork.Drive.Pages.Data;
public class VersionPageData : IPageDataProvider
{
public bool CanHandlePath(PathString path) => true;
public Task<IDictionary<string, object?>> GetAppDataAsync(HttpContext context)
{
var versionData = new AppVersion
{
Version = ThisAssembly.AssemblyVersion,
Commit = ThisAssembly.GitCommitId,
UpdateDate = ThisAssembly.GitCommitDate
};
var result = typeof(AppVersion).GetProperties()
.ToDictionary(property => property.Name, property => property.GetValue(versionData));
return Task.FromResult<IDictionary<string, object?>>(result);
}
}

View File

@@ -1,9 +1,7 @@
using DysonNetwork.Drive; using DysonNetwork.Drive;
using DysonNetwork.Drive.Pages.Data;
using DysonNetwork.Drive.Startup; using DysonNetwork.Drive.Startup;
using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Http;
using DysonNetwork.Shared.PageData;
using DysonNetwork.Shared.Registry; using DysonNetwork.Shared.Registry;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using tusdotnet.Stores; using tusdotnet.Stores;
@@ -35,8 +33,6 @@ builder.Services.AddAppBusinessServices();
// Add scheduled jobs // Add scheduled jobs
builder.Services.AddAppScheduledJobs(); builder.Services.AddAppScheduledJobs();
builder.Services.AddTransient<IPageDataProvider, VersionPageData>();
var app = builder.Build(); var app = builder.Build();
app.MapDefaultEndpoints(); app.MapDefaultEndpoints();
@@ -48,13 +44,6 @@ using (var scope = app.Services.CreateScope())
await db.Database.MigrateAsync(); await db.Database.MigrateAsync();
} }
var tusDiskStore = app.Services.GetRequiredService<TusDiskStore>();
// Configure application middleware pipeline
app.ConfigureAppMiddleware(tusDiskStore, builder.Environment.ContentRootPath);
app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html"));
// Configure gRPC // Configure gRPC
app.ConfigureGrpcServices(); app.ConfigureGrpcServices();

View File

@@ -7,7 +7,7 @@ namespace DysonNetwork.Drive.Startup;
public static class ApplicationBuilderExtensions public static class ApplicationBuilderExtensions
{ {
public static WebApplication ConfigureAppMiddleware(this WebApplication app, ITusStore tusStore, string contentRoot) public static WebApplication ConfigureAppMiddleware(this WebApplication app, ITusStore tusStore)
{ {
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
@@ -28,12 +28,6 @@ public static class ApplicationBuilderExtensions
.AllowAnyMethod() .AllowAnyMethod()
); );
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "wwwroot", "dist"))
});
app.MapTus("/api/tus", _ => Task.FromResult(TusService.BuildConfiguration(tusStore, app.Configuration))); app.MapTus("/api/tus", _ => Task.FromResult(TusService.BuildConfiguration(tusStore, app.Configuration)));
return app; return app;

View File

@@ -2,8 +2,8 @@ using System.Globalization;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Auth;
using DysonNetwork.Pass.Auth.OpenId; using DysonNetwork.Pass.Auth.OpenId;
using DysonNetwork.Pass.Email;
using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Localization;
using DysonNetwork.Pass.Mailer;
using DysonNetwork.Pass.Permission; using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Data;
@@ -452,7 +452,7 @@ public class AccountService(
} }
await mailer await mailer
.SendTemplatedEmailAsync<Pages.Emails.VerificationEmail, VerificationEmailModel>( .SendTemplatedEmailAsync<Emails.VerificationEmail, VerificationEmailModel>(
account.Nick, account.Nick,
contact.Content, contact.Content,
emailLocalizer["VerificationEmail"], emailLocalizer["VerificationEmail"],

View File

@@ -1,7 +1,7 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Pass.Email; using DysonNetwork.Pass.Emails;
using DysonNetwork.Pass.Pages.Emails; using DysonNetwork.Pass.Mailer;
using DysonNetwork.Pass.Permission; using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;

View File

@@ -135,14 +135,5 @@
<LastGenOutput>SharedResource.Designer.cs</LastGenOutput> <LastGenOutput>SharedResource.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Pages\Emails\AccountDeletionEmail.razor" />
<AdditionalFiles Include="Pages\Emails\ContactVerificationEmail.razor" />
<AdditionalFiles Include="Pages\Emails\EmailLayout.razor" />
<AdditionalFiles Include="Pages\Emails\LandingEmail.razor" />
<AdditionalFiles Include="Pages\Emails\PasswordResetEmail.razor" />
<AdditionalFiles Include="Pages\Emails\VerificationEmail.razor" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,4 +1,4 @@
namespace DysonNetwork.Pass.Email; namespace DysonNetwork.Pass.Mailer;
public class LandingEmailModel public class LandingEmailModel
{ {

View File

@@ -1,7 +1,7 @@
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace DysonNetwork.Pass.Email; namespace DysonNetwork.Pass.Mailer;
public class EmailService( public class EmailService(
RingService.RingServiceClient pusher, RingService.RingServiceClient pusher,

View File

@@ -1,15 +1,7 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
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.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using RouteData = Microsoft.AspNetCore.Routing.RouteData;
namespace DysonNetwork.Pass.Email; namespace DysonNetwork.Pass.Mailer;
public class RazorViewRenderer( public class RazorViewRenderer(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,

View File

@@ -1,52 +0,0 @@
using System.Net;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.PageData;
using Microsoft.EntityFrameworkCore;
using OpenGraphNet;
namespace DysonNetwork.Pass.Pages.Data;
public class AccountPageData(AppDatabase db, SubscriptionService subscriptions, IConfiguration configuration)
: IPageDataProvider
{
private readonly string _siteUrl = configuration["SiteUrl"]!;
public bool CanHandlePath(PathString path) =>
path.StartsWithSegments("/accounts") || path.ToString().StartsWith("/@");
public async Task<IDictionary<string, object?>> GetAppDataAsync(HttpContext context)
{
var path = context.Request.Path.Value!;
var startIndex = path.StartsWith("/accounts/") ? "/accounts/".Length : "/@".Length;
var endIndex = path.IndexOf('/', startIndex);
var username = endIndex == -1 ? path[startIndex..] : path.Substring(startIndex, endIndex - startIndex);
username = WebUtility.UrlDecode(username);
if (username.StartsWith("@"))
username = username[1..];
var account = await db.Accounts
.Include(e => e.Badges)
.Include(e => e.Profile)
.Where(a => a.Name == username)
.FirstOrDefaultAsync();
if (account is null) return new Dictionary<string, object?>();
var perk = await subscriptions.GetPerkSubscriptionAsync(account.Id);
account.PerkSubscription = perk?.ToReference();
var og = OpenGraph.MakeGraph(
title: account.Nick,
type: "profile",
image: $"{_siteUrl}/cgi/drive/files/{account.Profile.Picture?.Id}?original=true",
url: $"{_siteUrl}/@{username}",
description: account.Profile.Bio ?? $"@{account.Name} profile on the Solar Network",
siteName: "Solarpass"
);
return new Dictionary<string, object?>()
{
["Account"] = account,
["OpenGraph"] = og
};
}
}

View File

@@ -1,20 +0,0 @@
using DysonNetwork.Shared.PageData;
namespace DysonNetwork.Pass.Pages.Data;
public class CaptchaPageData(IConfiguration configuration) : IPageDataProvider
{
public bool CanHandlePath(PathString path) => path == "/captcha";
public Task<IDictionary<string, object?>> GetAppDataAsync(HttpContext context)
{
var provider = configuration.GetSection("Captcha")["Provider"]?.ToLower();
var apiKey = configuration.GetSection("Captcha")["ApiKey"];
return Task.FromResult<IDictionary<string, object?>>(new Dictionary<string, object?>
{
["Provider"] = provider,
["ApiKey"] = apiKey
});
}
}

View File

@@ -1,24 +0,0 @@
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.PageData;
namespace DysonNetwork.Pass.Pages.Data;
public class VersionPageData : IPageDataProvider
{
public bool CanHandlePath(PathString path) => true;
public Task<IDictionary<string, object?>> GetAppDataAsync(HttpContext context)
{
var versionData = new AppVersion
{
Version = ThisAssembly.AssemblyVersion,
Commit = ThisAssembly.GitCommitId,
UpdateDate = ThisAssembly.GitCommitDate
};
var result = typeof(AppVersion).GetProperties()
.ToDictionary(property => property.Name, property => property.GetValue(versionData));
return Task.FromResult<IDictionary<string, object?>>(result);
}
}

View File

@@ -1,8 +1,6 @@
using DysonNetwork.Pass; using DysonNetwork.Pass;
using DysonNetwork.Pass.Pages.Data;
using DysonNetwork.Pass.Startup; using DysonNetwork.Pass.Startup;
using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Http;
using DysonNetwork.Shared.PageData;
using DysonNetwork.Shared.Registry; using DysonNetwork.Shared.Registry;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -31,10 +29,6 @@ builder.Services.AddAppBusinessServices(builder.Configuration);
// Add scheduled jobs // Add scheduled jobs
builder.Services.AddAppScheduledJobs(); builder.Services.AddAppScheduledJobs();
builder.Services.AddTransient<IPageDataProvider, VersionPageData>();
builder.Services.AddTransient<IPageDataProvider, CaptchaPageData>();
builder.Services.AddTransient<IPageDataProvider, AccountPageData>();
var app = builder.Build(); var app = builder.Build();
app.MapDefaultEndpoints(); app.MapDefaultEndpoints();
@@ -47,9 +41,7 @@ using (var scope = app.Services.CreateScope())
} }
// Configure application middleware pipeline // Configure application middleware pipeline
app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath); app.ConfigureAppMiddleware(builder.Configuration);
app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html"));
// Configure gRPC // Configure gRPC
app.ConfigureGrpcServices(); app.ConfigureGrpcServices();

View File

@@ -14,7 +14,7 @@ namespace DysonNetwork.Pass.Startup;
public static class ApplicationConfiguration public static class ApplicationConfiguration
{ {
public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration, string contentRoot) public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration)
{ {
app.MapMetrics(); app.MapMetrics();
app.MapOpenApi(); app.MapOpenApi();
@@ -41,12 +41,6 @@ public static class ApplicationConfiguration
app.UseAuthorization(); app.UseAuthorization();
app.UseMiddleware<PermissionMiddleware>(); app.UseMiddleware<PermissionMiddleware>();
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "wwwroot", "dist"))
});
app.MapControllers().RequireRateLimiting("fixed"); app.MapControllers().RequireRateLimiting("fixed");
return app; return app;

View File

@@ -2,7 +2,6 @@ using System.Globalization;
using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Auth;
using DysonNetwork.Pass.Auth.OpenId; using DysonNetwork.Pass.Auth.OpenId;
using DysonNetwork.Pass.Email;
using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Localization;
using DysonNetwork.Pass.Permission; using DysonNetwork.Pass.Permission;
using DysonNetwork.Pass.Wallet; using DysonNetwork.Pass.Wallet;
@@ -19,6 +18,7 @@ using DysonNetwork.Pass.Auth.OidcProvider.Services;
using DysonNetwork.Pass.Credit; using DysonNetwork.Pass.Credit;
using DysonNetwork.Pass.Handlers; using DysonNetwork.Pass.Handlers;
using DysonNetwork.Pass.Leveling; using DysonNetwork.Pass.Leveling;
using DysonNetwork.Pass.Mailer;
using DysonNetwork.Pass.Safety; using DysonNetwork.Pass.Safety;
using DysonNetwork.Pass.Wallet.PaymentHandlers; using DysonNetwork.Pass.Wallet.PaymentHandlers;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;

View File

@@ -1,9 +0,0 @@
using Microsoft.AspNetCore.Http;
namespace DysonNetwork.Shared.PageData;
public interface IPageDataProvider
{
bool CanHandlePath(PathString path);
Task<IDictionary<string, object?>> GetAppDataAsync(HttpContext context);
}

View File

@@ -1,80 +0,0 @@
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using NodaTime;
using NodaTime.Serialization.SystemTextJson;
using OpenGraphNet;
namespace DysonNetwork.Shared.PageData;
public static class PageStartup
{
/// <summary>
/// The method setup the single page application routes for you.
/// Before you calling this, ensure you have setup the static files and default files:
/// <code>
/// app.UseDefaultFiles();
/// app.UseStaticFiles(new StaticFileOptions
/// {
/// FileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "wwwroot", "dist"))
/// });
/// </code>
/// </summary>
/// <param name="app"></param>
/// <param name="defaultFile"></param>
/// <returns></returns>
public static WebApplication MapPages(this WebApplication app, string defaultFile)
{
var jsonOpts = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower,
PropertyNameCaseInsensitive = true,
}.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
#pragma warning disable ASP0016
app.MapFallback(async context =>
{
if (context.Request.Path.StartsWithSegments("/api") || context.Request.Path.StartsWithSegments("/cgi"))
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
await context.Response.WriteAsync("Not found");
return;
}
var html = await File.ReadAllTextAsync(defaultFile);
using var scope = app.Services.CreateScope();
var providers = scope.ServiceProvider.GetServices<IPageDataProvider>();
var matches = providers
.Where(p => p.CanHandlePath(context.Request.Path))
.Select(p => p.GetAppDataAsync(context))
.ToList();
var results = await Task.WhenAll(matches);
var appData = new Dictionary<string, object?>();
foreach (var result in results)
foreach (var (key, value) in result)
appData[key] = value;
OpenGraph? og = null;
if (appData.TryGetValue("OpenGraph", out var openGraph) && openGraph is OpenGraph gog)
og = gog;
var json = JsonSerializer.Serialize(appData, jsonOpts);
html = html.Replace("<app-data />", $"<script>window.DyPrefetch = {json};</script>");
if (og is not null)
html = html.Replace("<og-data />", og.ToString());
context.Response.ContentType = "text/html";
await context.Response.WriteAsync(html);
});
#pragma warning restore ASP0016
return app;
}
}

View File

@@ -1,72 +0,0 @@
using System.Net;
using DysonNetwork.Shared.PageData;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Sphere.Post;
using Microsoft.EntityFrameworkCore;
using OpenGraphNet;
namespace DysonNetwork.Sphere.PageData;
public class PostPageData(
AppDatabase db,
AccountService.AccountServiceClient accounts,
Publisher.PublisherService pub,
PostService ps,
IConfiguration configuration
)
: IPageDataProvider
{
private readonly string _siteUrl = configuration["SiteUrl"]!;
public bool CanHandlePath(PathString path) =>
path.StartsWithSegments("/posts");
public async Task<IDictionary<string, object?>> GetAppDataAsync(HttpContext context)
{
var path = context.Request.Path.Value!;
var startIndex = "/posts/".Length;
var endIndex = path.IndexOf('/', startIndex);
var slug = endIndex == -1 ? path[startIndex..] : path.Substring(startIndex, endIndex - startIndex);
slug = WebUtility.UrlDecode(slug);
var postId = Guid.TryParse(slug, out var postIdGuid) ? postIdGuid : Guid.Empty;
if (postId == Guid.Empty) return new Dictionary<string, object?>();
context.Items.TryGetValue("CurrentUser", out var currentUserValue);
var currentUser = currentUserValue as Account;
List<Guid> userFriends = [];
if (currentUser != null)
{
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
{ AccountId = currentUser.Id });
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
}
var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(Guid.Parse(currentUser.Id));
var post = await db.Posts
.Where(e => e.Id == postId)
.Include(e => e.Publisher)
.Include(e => e.Tags)
.Include(e => e.Categories)
.FilterWithVisibility(currentUser, userFriends, userPublishers)
.FirstOrDefaultAsync();
if (post == null) return new Dictionary<string, object?>();
post = await ps.LoadPostInfo(post, currentUser);
var og = OpenGraph.MakeGraph(
title: post.Title ?? $"Post from {post.Publisher.Name}",
type: "article",
image: $"{_siteUrl}/cgi/drive/files/{post.Publisher.Background?.Id}?original=true",
url: $"{_siteUrl}/@{slug}",
description: post.Description ?? (post.Content?.Length > 80 ? post.Content?[..80] : post.Content) ?? "Posted with some media",
siteName: "Solar Network"
);
return new Dictionary<string, object?>()
{
["Post"] = post,
["OpenGraph"] = og
};
}
}

View File

@@ -1,9 +1,7 @@
using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Http;
using DysonNetwork.Shared.PageData;
using DysonNetwork.Shared.Registry; using DysonNetwork.Shared.Registry;
using DysonNetwork.Sphere; using DysonNetwork.Sphere;
using DysonNetwork.Sphere.PageData;
using DysonNetwork.Sphere.Startup; using DysonNetwork.Sphere.Startup;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
@@ -35,8 +33,6 @@ builder.Services.AddAppBusinessServices(builder.Configuration);
// Add scheduled jobs // Add scheduled jobs
builder.Services.AddAppScheduledJobs(); builder.Services.AddAppScheduledJobs();
builder.Services.AddTransient<IPageDataProvider, PostPageData>();
var app = builder.Build(); var app = builder.Build();
app.MapDefaultEndpoints(); app.MapDefaultEndpoints();
@@ -51,12 +47,4 @@ using (var scope = app.Services.CreateScope())
// Configure application middleware pipeline // Configure application middleware pipeline
app.ConfigureAppMiddleware(builder.Configuration); app.ConfigureAppMiddleware(builder.Configuration);
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "wwwroot", "dist"))
});
app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html"));
app.Run(); app.Run();