🗑️ Remove built-in frontend serving code
This commit is contained in:
		@@ -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);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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"],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -136,13 +136,4 @@
 | 
				
			|||||||
        </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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
namespace DysonNetwork.Pass.Email;
 | 
					namespace DysonNetwork.Pass.Mailer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class LandingEmailModel
 | 
					public class LandingEmailModel
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -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,
 | 
				
			||||||
@@ -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,
 | 
				
			||||||
@@ -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
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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); 
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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();
 | 
				
			||||||
		Reference in New Issue
	
	Block a user