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);