diff --git a/DysonNetwork.Pass/Client/index.html b/DysonNetwork.Pass/Client/index.html index f4a51a4..b06f124 100644 --- a/DysonNetwork.Pass/Client/index.html +++ b/DysonNetwork.Pass/Client/index.html @@ -6,6 +6,7 @@ Solarpass +
diff --git a/DysonNetwork.Pass/Client/src/dy-prefetch.d.ts b/DysonNetwork.Pass/Client/src/dy-prefetch.d.ts new file mode 100644 index 0000000..ba91e69 --- /dev/null +++ b/DysonNetwork.Pass/Client/src/dy-prefetch.d.ts @@ -0,0 +1,7 @@ +export {} + +declare global { + interface Window { + DyPrefetch?: any + } +} diff --git a/DysonNetwork.Pass/Client/src/views/captcha.vue b/DysonNetwork.Pass/Client/src/views/captcha.vue index cf925f1..36b3e0f 100644 --- a/DysonNetwork.Pass/Client/src/views/captcha.vue +++ b/DysonNetwork.Pass/Client/src/views/captcha.vue @@ -46,8 +46,8 @@ import CaptchaWidget from '@/components/CaptchaWidget.vue'; const route = useRoute(); // Get provider and API key from app data -const provider = ref((window as any).__APP_DATA__?.Provider || ''); -const apiKey = ref((window as any).__APP_DATA__?.ApiKey || ''); +const provider = ref(window.DyPrefetch?.provider || ''); +const apiKey = ref(window.DyPrefetch?.api_key || ''); const onCaptchaVerified = (token: string) => { if (window.parent !== window) { diff --git a/DysonNetwork.Pass/Client/src/views/create-account.vue b/DysonNetwork.Pass/Client/src/views/create-account.vue index 6e6380c..2c6ca89 100644 --- a/DysonNetwork.Pass/Client/src/views/create-account.vue +++ b/DysonNetwork.Pass/Client/src/views/create-account.vue @@ -116,8 +116,8 @@ const rules: FormRules = { } // Get captcha provider and API key from global data -const captchaProvider = ref((window as any).__APP_DATA__?.Provider || '') -const captchaApiKey = ref((window as any).__APP_DATA__?.ApiKey || '') +const captchaProvider = ref(window.DyPrefetch?.provider || '') +const captchaApiKey = ref(window.DyPrefetch?.api_key || '') const onCaptchaVerified = (token: string) => { formModel.captchaToken = token diff --git a/DysonNetwork.Pass/Client/src/views/pfp/index.vue b/DysonNetwork.Pass/Client/src/views/pfp/index.vue index e58c249..fff663f 100644 --- a/DysonNetwork.Pass/Client/src/views/pfp/index.vue +++ b/DysonNetwork.Pass/Client/src/views/pfp/index.vue @@ -175,11 +175,9 @@ const notFound = ref(false) const user = ref(null) async function fetchUser() { - // @ts-ignore - if (window.__APP_DATA__?.Account != null) { + if (window.DyPrefetch?.Account != null) { console.log('[Fetch] Use the pre-rendered account data.') - // @ts-ignore - user.value = window.__APP_DATA__['Account'] + user.value = window.DyPrefetch.Account return } diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index 6d2fd9c..fc73b38 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -25,6 +25,7 @@ + diff --git a/DysonNetwork.Pass/Pages/Data/AccountPageData.cs b/DysonNetwork.Pass/Pages/Data/AccountPageData.cs new file mode 100644 index 0000000..f0a66ed --- /dev/null +++ b/DysonNetwork.Pass/Pages/Data/AccountPageData.cs @@ -0,0 +1,52 @@ +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> 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(); + + 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() + { + ["Account"] = account, + ["OpenGraph"] = og + }; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 4dbafa7..32cdc11 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -34,6 +34,7 @@ builder.Services.AddAppScheduledJobs(); builder.Services.AddTransient(); builder.Services.AddTransient(); +builder.Services.AddTransient(); var app = builder.Build(); diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index 0374780..1117727 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -1,6 +1,7 @@ { "Debug": true, "BaseUrl": "http://localhost:5216", + "SiteUrl": "https://id.solian.app", "Logging": { "LogLevel": { "Default": "Information", diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 03783fe..907c25b 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -26,6 +26,7 @@ + diff --git a/DysonNetwork.Shared/PageData/Startup.cs b/DysonNetwork.Shared/PageData/Startup.cs index 3e463a8..5d928f0 100644 --- a/DysonNetwork.Shared/PageData/Startup.cs +++ b/DysonNetwork.Shared/PageData/Startup.cs @@ -3,6 +3,9 @@ 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; @@ -24,6 +27,13 @@ public static class PageStartup /// 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 => { @@ -33,7 +43,7 @@ public static class PageStartup await context.Response.WriteAsync("Not found"); return; } - + var html = await File.ReadAllTextAsync(defaultFile); using var scope = app.Services.CreateScope(); @@ -50,8 +60,15 @@ public static class PageStartup foreach (var (key, value) in result) appData[key] = value; - var json = JsonSerializer.Serialize(appData); - html = html.Replace("", $""); + 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("", $""); + + if (og is not null) + html = html.Replace("", og.ToString()); context.Response.ContentType = "text/html"; await context.Response.WriteAsync(html);