From a16da372218999525b25701913ee64e800d09c12 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 22 Nov 2025 01:47:10 +0800 Subject: [PATCH] :sparkles: Account about page --- .../Account/AccountCurrentController.cs | 2 +- .../Account/AccountServiceGrpc.cs | 5 +- ...1003061315_AddSubscriptionGift.Designer.cs | 2 +- ...3_RefactorSubscriptionRelation.Designer.cs | 2 +- .../20251003152102_AddWalletFund.Designer.cs | 2 +- ...0251008050851_AddUsernameColor.Designer.cs | 2 +- ...51021153439_AddRealmFromSphere.Designer.cs | 2 +- .../20251022164134_RemoveChatRoom.Designer.cs | 2 +- .../20251023173204_AddLotteries.Designer.cs | 2 +- ...54539_AddDetailLotteriesStatus.Designer.cs | 2 +- ...1101092348_AddPresenceActivity.Designer.cs | 2 +- ...1142549_EnrichPresenceActivity.Designer.cs | 2 +- ...00_AddSocialCreditRecordStatus.Designer.cs | 2 +- .../20251116153151_OpenableFunds.Designer.cs | 2 +- ...116163407_OpenFundsTotalSplits.Designer.cs | 2 +- .../Migrations/AppDatabaseModelSnapshot.cs | 2 +- DysonNetwork.Shared/Models/Account.cs | 76 ++++-- DysonNetwork.Shared/Proto/account.proto | 6 + DysonNetwork.Zone/Caddyfile | 7 + DysonNetwork.Zone/DysonNetwork.Zone.csproj | 1 + DysonNetwork.Zone/Pages/About.cshtml | 243 ++++++++++++++++++ DysonNetwork.Zone/Pages/About.cshtml.cs | 89 +++++++ DysonNetwork.Zone/Pages/Index.cshtml | 4 +- DysonNetwork.Zone/Pages/Shared/_Layout.cshtml | 2 + .../Pages/Shared/_LayoutContained.cshtml | 37 +++ DysonNetwork.Zone/Program.cs | 2 + 26 files changed, 462 insertions(+), 40 deletions(-) create mode 100644 DysonNetwork.Zone/Pages/About.cshtml create mode 100644 DysonNetwork.Zone/Pages/About.cshtml.cs create mode 100644 DysonNetwork.Zone/Pages/Shared/_LayoutContained.cshtml diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs index db510d1..73492e3 100644 --- a/DysonNetwork.Pass/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -82,7 +82,7 @@ public class AccountCurrentController( [MaxLength(4096)] public string? Bio { get; set; } public Shared.Models.UsernameColor? UsernameColor { get; set; } public Instant? Birthday { get; set; } - public List? Links { get; set; } + public List? Links { get; set; } [MaxLength(32)] public string? PictureId { get; set; } [MaxLength(32)] public string? BackgroundId { get; set; } diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs index 2636d49..d4f235a 100644 --- a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -24,15 +24,16 @@ public class AccountServiceGrpc( public override async Task GetAccount(GetAccountRequest request, ServerCallContext context) { if (!Guid.TryParse(request.Id, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid account ID format")); var account = await _db.Accounts .AsNoTracking() .Include(a => a.Profile) + .Include(a => a.Contacts.Where(c => c.IsPublic)) .FirstOrDefaultAsync(a => a.Id == accountId); if (account == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found")); + throw new RpcException(new Status(StatusCode.NotFound, $"Account {request.Id} not found")); var perk = await subscriptions.GetPerkSubscriptionAsync(account.Id); account.PerkSubscription = perk?.ToReference(); diff --git a/DysonNetwork.Pass/Migrations/20251003061315_AddSubscriptionGift.Designer.cs b/DysonNetwork.Pass/Migrations/20251003061315_AddSubscriptionGift.Designer.cs index 649e6b1..f8a4ae1 100644 --- a/DysonNetwork.Pass/Migrations/20251003061315_AddSubscriptionGift.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251003061315_AddSubscriptionGift.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251003123103_RefactorSubscriptionRelation.Designer.cs b/DysonNetwork.Pass/Migrations/20251003123103_RefactorSubscriptionRelation.Designer.cs index af76da9..53824fd 100644 --- a/DysonNetwork.Pass/Migrations/20251003123103_RefactorSubscriptionRelation.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251003123103_RefactorSubscriptionRelation.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251003152102_AddWalletFund.Designer.cs b/DysonNetwork.Pass/Migrations/20251003152102_AddWalletFund.Designer.cs index cb41010..d6defee 100644 --- a/DysonNetwork.Pass/Migrations/20251003152102_AddWalletFund.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251003152102_AddWalletFund.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251008050851_AddUsernameColor.Designer.cs b/DysonNetwork.Pass/Migrations/20251008050851_AddUsernameColor.Designer.cs index a52ff88..92fc365 100644 --- a/DysonNetwork.Pass/Migrations/20251008050851_AddUsernameColor.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251008050851_AddUsernameColor.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251021153439_AddRealmFromSphere.Designer.cs b/DysonNetwork.Pass/Migrations/20251021153439_AddRealmFromSphere.Designer.cs index c9e0877..e435aea 100644 --- a/DysonNetwork.Pass/Migrations/20251021153439_AddRealmFromSphere.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251021153439_AddRealmFromSphere.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251022164134_RemoveChatRoom.Designer.cs b/DysonNetwork.Pass/Migrations/20251022164134_RemoveChatRoom.Designer.cs index 1558b01..4babc35 100644 --- a/DysonNetwork.Pass/Migrations/20251022164134_RemoveChatRoom.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251022164134_RemoveChatRoom.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251023173204_AddLotteries.Designer.cs b/DysonNetwork.Pass/Migrations/20251023173204_AddLotteries.Designer.cs index 2e07f30..c7e4f3b 100644 --- a/DysonNetwork.Pass/Migrations/20251023173204_AddLotteries.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251023173204_AddLotteries.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251024154539_AddDetailLotteriesStatus.Designer.cs b/DysonNetwork.Pass/Migrations/20251024154539_AddDetailLotteriesStatus.Designer.cs index 9dd1205..5bdd5ce 100644 --- a/DysonNetwork.Pass/Migrations/20251024154539_AddDetailLotteriesStatus.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251024154539_AddDetailLotteriesStatus.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251101092348_AddPresenceActivity.Designer.cs b/DysonNetwork.Pass/Migrations/20251101092348_AddPresenceActivity.Designer.cs index 2933486..4fdb63e 100644 --- a/DysonNetwork.Pass/Migrations/20251101092348_AddPresenceActivity.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251101092348_AddPresenceActivity.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251101142549_EnrichPresenceActivity.Designer.cs b/DysonNetwork.Pass/Migrations/20251101142549_EnrichPresenceActivity.Designer.cs index db6399f..581cbdb 100644 --- a/DysonNetwork.Pass/Migrations/20251101142549_EnrichPresenceActivity.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251101142549_EnrichPresenceActivity.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251101175500_AddSocialCreditRecordStatus.Designer.cs b/DysonNetwork.Pass/Migrations/20251101175500_AddSocialCreditRecordStatus.Designer.cs index c2d924e..0b0c653 100644 --- a/DysonNetwork.Pass/Migrations/20251101175500_AddSocialCreditRecordStatus.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251101175500_AddSocialCreditRecordStatus.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251116153151_OpenableFunds.Designer.cs b/DysonNetwork.Pass/Migrations/20251116153151_OpenableFunds.Designer.cs index c43fd62..ff63da6 100644 --- a/DysonNetwork.Pass/Migrations/20251116153151_OpenableFunds.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251116153151_OpenableFunds.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/20251116163407_OpenFundsTotalSplits.Designer.cs b/DysonNetwork.Pass/Migrations/20251116163407_OpenFundsTotalSplits.Designer.cs index 67b52b1..aa4876c 100644 --- a/DysonNetwork.Pass/Migrations/20251116163407_OpenFundsTotalSplits.Designer.cs +++ b/DysonNetwork.Pass/Migrations/20251116163407_OpenFundsTotalSplits.Designer.cs @@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs index ddb66be..8508a60 100644 --- a/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs @@ -442,7 +442,7 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_seen_at"); - b.Property>("Links") + b.Property>("Links") .HasColumnType("jsonb") .HasColumnName("links"); diff --git a/DysonNetwork.Shared/Models/Account.cs b/DysonNetwork.Shared/Models/Account.cs index 1348c0d..557a9ad 100644 --- a/DysonNetwork.Shared/Models/Account.cs +++ b/DysonNetwork.Shared/Models/Account.cs @@ -121,22 +121,23 @@ public abstract class Leveling { if (xp < 0) return 0; - int level = 0; + var level = 0; while (level < MaxLevel && TotalExpForLevel(level + 1) <= xp) { level++; } + return level; } // Progress to next level (0.0 ~ 1.0) public static double GetProgressToNextLevel(int xp) { - int currentLevel = GetLevelFromExp(xp); + var currentLevel = GetLevelFromExp(xp); if (currentLevel >= MaxLevel) return 1.0; - double prevTotal = TotalExpForLevel(currentLevel); - double nextTotal = TotalExpForLevel(currentLevel + 1); + var prevTotal = TotalExpForLevel(currentLevel); + var nextTotal = TotalExpForLevel(currentLevel + 1); return (xp - prevTotal) / (nextTotal - prevTotal); } @@ -145,9 +146,9 @@ public abstract class Leveling public class UsernameColor { public string Type { get; set; } = "plain"; // "plain" | "gradient" - public string? Value { get; set; } // e.g. "red" or "#ff6600" - public string? Direction { get; set; } // e.g. "to right" - public List? Colors { get; set; } // e.g. ["#ff0000", "#00ff00"] + public string? Value { get; set; } // e.g. "red" or "#ff6600" + public string? Direction { get; set; } // e.g. "to right" + public List? Colors { get; set; } // e.g. ["#ff0000", "#00ff00"] public Proto.UsernameColor ToProtoValue() { @@ -161,6 +162,7 @@ public class UsernameColor { proto.Colors.AddRange(Colors); } + return proto; } @@ -187,7 +189,7 @@ public class SnAccountProfile : ModelBase, IIdentifiedResource [MaxLength(1024)] public string? Pronouns { get; set; } [MaxLength(1024)] public string? TimeZone { get; set; } [MaxLength(1024)] public string? Location { get; set; } - [Column(TypeName = "jsonb")] public List? Links { get; set; } + [Column(TypeName = "jsonb")] public List? Links { get; set; } [Column(TypeName = "jsonb")] public UsernameColor? UsernameColor { get; set; } public Instant? Birthday { get; set; } public Instant? LastSeenAt { get; set; } @@ -197,10 +199,8 @@ public class SnAccountProfile : ModelBase, IIdentifiedResource public int Experience { get; set; } - [NotMapped] - public int Level => Leveling.GetLevelFromExp(Experience); - [NotMapped] - public double LevelingProgress => Leveling.GetProgressToNextLevel(Experience); + [NotMapped] public int Level => Leveling.GetLevelFromExp(Experience); + [NotMapped] public double LevelingProgress => Leveling.GetProgressToNextLevel(Experience); public double SocialCredits { get; set; } = 100; @@ -249,9 +249,13 @@ public class SnAccountProfile : ModelBase, IIdentifiedResource UpdatedAt = UpdatedAt.ToTimestamp() }; + if (Links is not null) + proto.Links.AddRange(Links.Select(l => l.ToProtoValue())); + return proto; } + public static SnAccountProfile FromProtoValue(Proto.AccountProfile proto) { var profile = new SnAccountProfile @@ -267,28 +271,54 @@ public class SnAccountProfile : ModelBase, IIdentifiedResource Location = proto.Location, Birthday = proto.Birthday?.ToInstant(), LastSeenAt = proto.LastSeenAt?.ToInstant(), - Verification = proto.Verification is null ? null : SnVerificationMark.FromProtoValue(proto.Verification), + Verification = + proto.Verification is null ? null : SnVerificationMark.FromProtoValue(proto.Verification), ActiveBadge = proto.ActiveBadge is null ? null : SnAccountBadgeRef.FromProtoValue(proto.ActiveBadge), Experience = proto.Experience, SocialCredits = proto.SocialCredits, Picture = proto.Picture is null ? null : SnCloudFileReferenceObject.FromProtoValue(proto.Picture), - Background = proto.Background is null ? null : SnCloudFileReferenceObject.FromProtoValue(proto.Background), + Background = proto.Background is null + ? null + : SnCloudFileReferenceObject.FromProtoValue(proto.Background), AccountId = Guid.Parse(proto.AccountId), - UsernameColor = proto.UsernameColor is not null ? UsernameColor.FromProtoValue(proto.UsernameColor) : null, + UsernameColor = proto.UsernameColor is not null + ? UsernameColor.FromProtoValue(proto.UsernameColor) + : null, CreatedAt = proto.CreatedAt.ToInstant(), UpdatedAt = proto.UpdatedAt.ToInstant() }; + if (proto.Links.Count > 0) + profile.Links = proto.Links.Select(SnProfileLink.FromProtoValue).ToList(); + return profile; } public string ResourceIdentifier => $"account:profile:{Id}"; } -public class ProfileLink +public class SnProfileLink { public string Name { get; set; } = string.Empty; public string Url { get; set; } = string.Empty; + + public Proto.ProfileLink ToProtoValue() + { + return new Proto.ProfileLink + { + Name = Name, + Url = Url + }; + } + + public static SnProfileLink FromProtoValue(Proto.ProfileLink proto) + { + return new SnProfileLink + { + Name = proto.Name, + Url = proto.Url + }; + } } public class SnAccountContact : ModelBase @@ -361,7 +391,7 @@ public class SnAccountAuthFactor : ModelBase { public Guid Id { get; set; } public AccountAuthFactorType Type { get; set; } - [JsonIgnore][MaxLength(8196)] public string? Secret { get; set; } + [JsonIgnore] [MaxLength(8196)] public string? Secret { get; set; } [JsonIgnore] [Column(TypeName = "jsonb")] @@ -398,11 +428,13 @@ public class SnAccountAuthFactor : ModelBase return BCrypt.Net.BCrypt.Verify(password, Secret); case AccountAuthFactorType.TimedCode: var otp = new Totp(Base32Encoding.ToBytes(Secret)); - return otp.VerifyTotp(DateTime.UtcNow, password, out _, new VerificationWindow(previous: 5, future: 5)); + return otp.VerifyTotp(DateTime.UtcNow, password, out _, + new VerificationWindow(previous: 5, future: 5)); case AccountAuthFactorType.EmailCode: case AccountAuthFactorType.InAppCode: default: - throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead."); + throw new InvalidOperationException( + "Unsupported verification type, use CheckDeliveredCode instead."); } } @@ -430,10 +462,10 @@ public class SnAccountConnection : ModelBase [MaxLength(8192)] public string ProvidedIdentifier { get; set; } = null!; [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } = []; - [JsonIgnore][MaxLength(4096)] public string? AccessToken { get; set; } - [JsonIgnore][MaxLength(4096)] public string? RefreshToken { get; set; } + [JsonIgnore] [MaxLength(4096)] public string? AccessToken { get; set; } + [JsonIgnore] [MaxLength(4096)] public string? RefreshToken { get; set; } public Instant? LastUsedAt { get; set; } public Guid AccountId { get; set; } public SnAccount Account { get; set; } = null!; -} +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index ed746d1..30cb6bc 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -66,6 +66,11 @@ message UsernameColor { repeated string colors = 4; } +message ProfileLink { + string name = 1; + string url = 2; +} + // Profile contains detailed information about a user message AccountProfile { string id = 1; @@ -97,6 +102,7 @@ message AccountProfile { google.protobuf.Timestamp created_at = 22; google.protobuf.Timestamp updated_at = 23; optional UsernameColor username_color = 24; + repeated ProfileLink links = 25; } // AccountContact represents a contact method for an account diff --git a/DysonNetwork.Zone/Caddyfile b/DysonNetwork.Zone/Caddyfile index 233cecc..c10c1e9 100644 --- a/DysonNetwork.Zone/Caddyfile +++ b/DysonNetwork.Zone/Caddyfile @@ -3,3 +3,10 @@ http://localhost:3001 { header_up X-SiteName "ciallo" } } + +http://localhost:3002 { + reverse_proxy /drive/* localhost:5001 + reverse_proxy localhost:8007 { + header_up X-SiteName "regular" + } +} diff --git a/DysonNetwork.Zone/DysonNetwork.Zone.csproj b/DysonNetwork.Zone/DysonNetwork.Zone.csproj index 4bb62e4..986f5ec 100644 --- a/DysonNetwork.Zone/DysonNetwork.Zone.csproj +++ b/DysonNetwork.Zone/DysonNetwork.Zone.csproj @@ -12,6 +12,7 @@ + all diff --git a/DysonNetwork.Zone/Pages/About.cshtml b/DysonNetwork.Zone/Pages/About.cshtml new file mode 100644 index 0000000..05e2cc1 --- /dev/null +++ b/DysonNetwork.Zone/Pages/About.cshtml @@ -0,0 +1,243 @@ +@page +@using NodaTime.Serialization.Protobuf +@model AboutModel + +@{ + Layout = "_LayoutContained"; +} + +@if (Model.UserAccount != null) +{ + @if (!string.IsNullOrEmpty(Model.UserBackgroundUrl)) + { + Background Image + } + +
+
+ @if (!string.IsNullOrEmpty(Model.UserPictureUrl)) + { + Avatar + } + else + { +
+ @(Model.UserAccount.Name != null ? Model.UserAccount.Name[0] : '?') +
+ } +
+
+ @(Model.UserAccount.Nick ?? Model.UserAccount.Name) +
+
@@@Model.UserAccount.Name
+
+
+ +
+
+
+
+

Info

+ +
+ @if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.TimeZone)) + { +
+ + + Time Zone + + + @Model.GetCurrentTimeInTimeZone(Model.UserAccount.Profile.TimeZone) + · + @Model.GetOffsetUtcString(Model.UserAccount.Profile.TimeZone) + · + @Model.UserAccount.Profile.TimeZone + +
+ } + @if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Location)) + { +
+ + Location + + @Model.UserAccount.Profile.Location +
+ } + @if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.FirstName) || !string.IsNullOrEmpty(Model.UserAccount.Profile?.LastName)) + { +
+ + Name + + + @string.Join(" ", new[] + { + Model.UserAccount.Profile.FirstName, + Model.UserAccount.Profile.MiddleName, + Model.UserAccount.Profile.LastName + }.Where(s => !string.IsNullOrEmpty(s))) + +
+ } + @if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Gender)) + { +
+ + Gender + + + @Model.UserAccount.Profile.Gender + @if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Pronouns)) + { + · + @Model.UserAccount.Profile.Pronouns + } + +
+ } +
+ + Joined at + + + @Model.UserAccount.CreatedAt?.ToDateTimeOffset().ToString("MMMM d, yyyy") + +
+ @if (Model.UserAccount.Profile?.Birthday != null) + { +
+ + Birthday + + + @Model.CalculateAge(Model.UserAccount.Profile.Birthday.ToInstant()) yrs old + · + @Model.UserAccount.Profile.Birthday?.ToDateTimeOffset().ToString("MMMM d") + +
+ } +
+
+
+ + @if (Model.UserAccount.PerkSubscription != null) + { +
+
+
+
+
+ @Model.GetPerkInfo(Model.UserAccount.PerkSubscription.Identifier).Name Tier +
+
Stellar Program Member
+
+
+ ★ +
+
+
+
+ } + +
+
+
+
Level @Model.UserAccount.Profile?.Level
+
@Model.UserAccount.Profile?.Experience XP
+
+ +
+
+
+ + + @if (Model.UserAccount.Profile?.Links.Count > 0) + { +
+
+
+

+ Links +

+
    + @foreach (var link in Model.UserAccount.Profile.Links) + { +
  • +
    +
    @(link.Name)
    +
    @(link.Url)
    +
    + + + +
  • + } +
+
+
+
+ } + + @if (Model.UserAccount.Contacts.Count > 0) + { +
+
+
+

+ Links +

+
    + @foreach (var contact in Model.UserAccount.Contacts) + { +
  • +
    +
    @(contact.Content)
    +
    @(contact.Type.ToString())
    +
    + +
  • + } +
+
+
+
+ } + +
+ @if (!string.IsNullOrEmpty(Model.HtmlBio)) + { +
+
+

+ Bio +

+
+ @Html.Raw(Model.HtmlBio) +
+
+
+ } +
+
+
+} +else +{ +
+
+
+
+ +
+

User not found

+

The user profile you're trying to access is not found.

+
+
+
+} diff --git a/DysonNetwork.Zone/Pages/About.cshtml.cs b/DysonNetwork.Zone/Pages/About.cshtml.cs new file mode 100644 index 0000000..ed034bf --- /dev/null +++ b/DysonNetwork.Zone/Pages/About.cshtml.cs @@ -0,0 +1,89 @@ +using DysonNetwork.Shared.Models; +using DysonNetwork.Shared.Proto; +using DysonNetwork.Shared.Registry; +using DysonNetwork.Zone.Publication; +using Markdig; +using Microsoft.AspNetCore.Mvc.RazorPages; +using NodaTime; + + +namespace DysonNetwork.Zone.Pages; + +public class AboutModel(RemoteAccountService ras) : PageModel +{ + public SnPublicationSite? Site { get; set; } + public Account? UserAccount { get; set; } + public string? HtmlBio { get; set; } + + public string? UserPictureUrl => UserAccount?.Profile?.Picture?.Id != null + ? $"/drive/files/{UserAccount.Profile.Picture.Id}" + : null; + + public string? UserBackgroundUrl => UserAccount?.Profile?.Background?.Id != null + ? $"/drive/files/{UserAccount.Profile.Background.Id}?original=true" + : null; + + public async Task OnGetAsync() + { + Site = HttpContext.Items[PublicationSiteMiddleware.SiteContextKey] as SnPublicationSite; + if (Site != null) + { + UserAccount = await ras.GetAccount(Site.AccountId); + + if (UserAccount?.Profile?.Bio != null) + { + var pipeline = new MarkdownPipelineBuilder().Build(); + HtmlBio = Markdown.ToHtml(UserAccount.Profile.Bio, pipeline); + } + } + } + + public int CalculateAge(Instant birthday) + { + var birthDate = birthday.ToDateTimeOffset(); + var today = DateTimeOffset.Now; + var age = today.Year - birthDate.Year; + if (birthDate > today.AddYears(-age)) age--; + return age; + } + + public string GetOffsetUtcString(string targetTimeZone) + { + try + { + var tz = TimeZoneInfo.FindSystemTimeZoneById(targetTimeZone); + var offset = tz.GetUtcOffset(DateTimeOffset.Now); + var sign = offset >= TimeSpan.Zero ? "+" : "-"; + return $"{sign}{Math.Abs(offset.Hours):D2}:{Math.Abs(offset.Minutes):D2}"; + } + catch + { + return "+00:00"; + } + } + + public string GetCurrentTimeInTimeZone(string targetTimeZone) + { + try + { + var tz = TimeZoneInfo.FindSystemTimeZoneById(targetTimeZone); + var now = TimeZoneInfo.ConvertTime(DateTimeOffset.Now, tz); + return now.ToString("t", System.Globalization.CultureInfo.InvariantCulture); + } + catch + { + return DateTime.Now.ToString("t", System.Globalization.CultureInfo.InvariantCulture); + } + } + + public (string Name, string Color) GetPerkInfo(string identifier) + { + return identifier switch + { + "solian.stellar.primary" => ("Stellar", "#2196f3"), + "solian.stellar.nova" => ("Nova", "#39c5bb"), + "solian.stellar.supernova" => ("Supernova", "#ffc109"), + _ => ("Unknown", "#2196f3") + }; + } +} diff --git a/DysonNetwork.Zone/Pages/Index.cshtml b/DysonNetwork.Zone/Pages/Index.cshtml index 8c2a406..235c908 100644 --- a/DysonNetwork.Zone/Pages/Index.cshtml +++ b/DysonNetwork.Zone/Pages/Index.cshtml @@ -1,10 +1,12 @@ @page @model IndexModel + @{ + Layout = "_LayoutContained"; ViewData["Title"] = "Solar Network Pages"; } -
+
Logo

Hello World 👋

diff --git a/DysonNetwork.Zone/Pages/Shared/_Layout.cshtml b/DysonNetwork.Zone/Pages/Shared/_Layout.cshtml index 1075d99..ded39b3 100644 --- a/DysonNetwork.Zone/Pages/Shared/_Layout.cshtml +++ b/DysonNetwork.Zone/Pages/Shared/_Layout.cshtml @@ -6,6 +6,8 @@ @ViewData["Title"] + + diff --git a/DysonNetwork.Zone/Pages/Shared/_LayoutContained.cshtml b/DysonNetwork.Zone/Pages/Shared/_LayoutContained.cshtml new file mode 100644 index 0000000..3c2ee86 --- /dev/null +++ b/DysonNetwork.Zone/Pages/Shared/_LayoutContained.cshtml @@ -0,0 +1,37 @@ +@using DysonNetwork.Zone.Publication +@using DysonNetwork.Shared.Models +@{ + Layout = "_Layout"; + var site = Context.Items[PublicationSiteMiddleware.SiteContextKey] as SnPublicationSite; + var siteDisplayName = site?.Name ?? "Solar Network"; +} + + + +
+ @RenderBody() +
+ + diff --git a/DysonNetwork.Zone/Program.cs b/DysonNetwork.Zone/Program.cs index 8d987eb..dc8dd7a 100644 --- a/DysonNetwork.Zone/Program.cs +++ b/DysonNetwork.Zone/Program.cs @@ -26,6 +26,8 @@ builder.Services.AddRingService(); builder.Services.AddAccountService(); builder.Services.AddSphereService(); +builder.Services.Configure(options => { options.LowercaseUrls = true; }); + builder.AddSwaggerManifest( "DysonNetwork.Zone", "The zone service in the Solar Network."