Compare commits
3 Commits
70a18b07ff
...
92a4899e7c
| Author | SHA1 | Date | |
|---|---|---|---|
|
92a4899e7c
|
|||
|
bdc8db3091
|
|||
|
a16da37221
|
@@ -82,7 +82,7 @@ public class AccountCurrentController(
|
|||||||
[MaxLength(4096)] public string? Bio { get; set; }
|
[MaxLength(4096)] public string? Bio { get; set; }
|
||||||
public Shared.Models.UsernameColor? UsernameColor { get; set; }
|
public Shared.Models.UsernameColor? UsernameColor { get; set; }
|
||||||
public Instant? Birthday { get; set; }
|
public Instant? Birthday { get; set; }
|
||||||
public List<ProfileLink>? Links { get; set; }
|
public List<SnProfileLink>? Links { get; set; }
|
||||||
|
|
||||||
[MaxLength(32)] public string? PictureId { get; set; }
|
[MaxLength(32)] public string? PictureId { get; set; }
|
||||||
[MaxLength(32)] public string? BackgroundId { get; set; }
|
[MaxLength(32)] public string? BackgroundId { get; set; }
|
||||||
|
|||||||
@@ -24,15 +24,16 @@ public class AccountServiceGrpc(
|
|||||||
public override async Task<Shared.Proto.Account> GetAccount(GetAccountRequest request, ServerCallContext context)
|
public override async Task<Shared.Proto.Account> GetAccount(GetAccountRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
if (!Guid.TryParse(request.Id, out var accountId))
|
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
|
var account = await _db.Accounts
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(a => a.Profile)
|
.Include(a => a.Profile)
|
||||||
|
.Include(a => a.Contacts.Where(c => c.IsPublic))
|
||||||
.FirstOrDefaultAsync(a => a.Id == accountId);
|
.FirstOrDefaultAsync(a => a.Id == accountId);
|
||||||
|
|
||||||
if (account == null)
|
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);
|
var perk = await subscriptions.GetPerkSubscriptionAsync(account.Id);
|
||||||
account.PerkSubscription = perk?.ToReference();
|
account.PerkSubscription = perk?.ToReference();
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -442,7 +442,7 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_seen_at");
|
.HasColumnName("last_seen_at");
|
||||||
|
|
||||||
b.Property<List<ProfileLink>>("Links")
|
b.Property<List<SnProfileLink>>("Links")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("links");
|
.HasColumnName("links");
|
||||||
|
|
||||||
|
|||||||
@@ -121,22 +121,23 @@ public abstract class Leveling
|
|||||||
{
|
{
|
||||||
if (xp < 0) return 0;
|
if (xp < 0) return 0;
|
||||||
|
|
||||||
int level = 0;
|
var level = 0;
|
||||||
while (level < MaxLevel && TotalExpForLevel(level + 1) <= xp)
|
while (level < MaxLevel && TotalExpForLevel(level + 1) <= xp)
|
||||||
{
|
{
|
||||||
level++;
|
level++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress to next level (0.0 ~ 1.0)
|
// Progress to next level (0.0 ~ 1.0)
|
||||||
public static double GetProgressToNextLevel(int xp)
|
public static double GetProgressToNextLevel(int xp)
|
||||||
{
|
{
|
||||||
int currentLevel = GetLevelFromExp(xp);
|
var currentLevel = GetLevelFromExp(xp);
|
||||||
if (currentLevel >= MaxLevel) return 1.0;
|
if (currentLevel >= MaxLevel) return 1.0;
|
||||||
|
|
||||||
double prevTotal = TotalExpForLevel(currentLevel);
|
var prevTotal = TotalExpForLevel(currentLevel);
|
||||||
double nextTotal = TotalExpForLevel(currentLevel + 1);
|
var nextTotal = TotalExpForLevel(currentLevel + 1);
|
||||||
|
|
||||||
return (xp - prevTotal) / (nextTotal - prevTotal);
|
return (xp - prevTotal) / (nextTotal - prevTotal);
|
||||||
}
|
}
|
||||||
@@ -161,6 +162,7 @@ public class UsernameColor
|
|||||||
{
|
{
|
||||||
proto.Colors.AddRange(Colors);
|
proto.Colors.AddRange(Colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
return proto;
|
return proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +189,7 @@ public class SnAccountProfile : ModelBase, IIdentifiedResource
|
|||||||
[MaxLength(1024)] public string? Pronouns { get; set; }
|
[MaxLength(1024)] public string? Pronouns { get; set; }
|
||||||
[MaxLength(1024)] public string? TimeZone { get; set; }
|
[MaxLength(1024)] public string? TimeZone { get; set; }
|
||||||
[MaxLength(1024)] public string? Location { get; set; }
|
[MaxLength(1024)] public string? Location { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public List<ProfileLink>? Links { get; set; }
|
[Column(TypeName = "jsonb")] public List<SnProfileLink>? Links { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public UsernameColor? UsernameColor { get; set; }
|
[Column(TypeName = "jsonb")] public UsernameColor? UsernameColor { get; set; }
|
||||||
public Instant? Birthday { get; set; }
|
public Instant? Birthday { get; set; }
|
||||||
public Instant? LastSeenAt { get; set; }
|
public Instant? LastSeenAt { get; set; }
|
||||||
@@ -197,10 +199,8 @@ public class SnAccountProfile : ModelBase, IIdentifiedResource
|
|||||||
|
|
||||||
public int Experience { get; set; }
|
public int Experience { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped] public int Level => Leveling.GetLevelFromExp(Experience);
|
||||||
public int Level => Leveling.GetLevelFromExp(Experience);
|
[NotMapped] public double LevelingProgress => Leveling.GetProgressToNextLevel(Experience);
|
||||||
[NotMapped]
|
|
||||||
public double LevelingProgress => Leveling.GetProgressToNextLevel(Experience);
|
|
||||||
|
|
||||||
public double SocialCredits { get; set; } = 100;
|
public double SocialCredits { get; set; } = 100;
|
||||||
|
|
||||||
@@ -249,9 +249,13 @@ public class SnAccountProfile : ModelBase, IIdentifiedResource
|
|||||||
UpdatedAt = UpdatedAt.ToTimestamp()
|
UpdatedAt = UpdatedAt.ToTimestamp()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Links is not null)
|
||||||
|
proto.Links.AddRange(Links.Select(l => l.ToProtoValue()));
|
||||||
|
|
||||||
return proto;
|
return proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static SnAccountProfile FromProtoValue(Proto.AccountProfile proto)
|
public static SnAccountProfile FromProtoValue(Proto.AccountProfile proto)
|
||||||
{
|
{
|
||||||
var profile = new SnAccountProfile
|
var profile = new SnAccountProfile
|
||||||
@@ -267,28 +271,54 @@ public class SnAccountProfile : ModelBase, IIdentifiedResource
|
|||||||
Location = proto.Location,
|
Location = proto.Location,
|
||||||
Birthday = proto.Birthday?.ToInstant(),
|
Birthday = proto.Birthday?.ToInstant(),
|
||||||
LastSeenAt = proto.LastSeenAt?.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),
|
ActiveBadge = proto.ActiveBadge is null ? null : SnAccountBadgeRef.FromProtoValue(proto.ActiveBadge),
|
||||||
Experience = proto.Experience,
|
Experience = proto.Experience,
|
||||||
SocialCredits = proto.SocialCredits,
|
SocialCredits = proto.SocialCredits,
|
||||||
Picture = proto.Picture is null ? null : SnCloudFileReferenceObject.FromProtoValue(proto.Picture),
|
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),
|
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(),
|
CreatedAt = proto.CreatedAt.ToInstant(),
|
||||||
UpdatedAt = proto.UpdatedAt.ToInstant()
|
UpdatedAt = proto.UpdatedAt.ToInstant()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (proto.Links.Count > 0)
|
||||||
|
profile.Links = proto.Links.Select(SnProfileLink.FromProtoValue).ToList();
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ResourceIdentifier => $"account:profile:{Id}";
|
public string ResourceIdentifier => $"account:profile:{Id}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ProfileLink
|
public class SnProfileLink
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string Url { 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
|
public class SnAccountContact : ModelBase
|
||||||
@@ -398,11 +428,13 @@ public class SnAccountAuthFactor : ModelBase
|
|||||||
return BCrypt.Net.BCrypt.Verify(password, Secret);
|
return BCrypt.Net.BCrypt.Verify(password, Secret);
|
||||||
case AccountAuthFactorType.TimedCode:
|
case AccountAuthFactorType.TimedCode:
|
||||||
var otp = new Totp(Base32Encoding.ToBytes(Secret));
|
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.EmailCode:
|
||||||
case AccountAuthFactorType.InAppCode:
|
case AccountAuthFactorType.InAppCode:
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead.");
|
throw new InvalidOperationException(
|
||||||
|
"Unsupported verification type, use CheckDeliveredCode instead.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ message UsernameColor {
|
|||||||
repeated string colors = 4;
|
repeated string colors = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ProfileLink {
|
||||||
|
string name = 1;
|
||||||
|
string url = 2;
|
||||||
|
}
|
||||||
|
|
||||||
// Profile contains detailed information about a user
|
// Profile contains detailed information about a user
|
||||||
message AccountProfile {
|
message AccountProfile {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
@@ -97,6 +102,7 @@ message AccountProfile {
|
|||||||
google.protobuf.Timestamp created_at = 22;
|
google.protobuf.Timestamp created_at = 22;
|
||||||
google.protobuf.Timestamp updated_at = 23;
|
google.protobuf.Timestamp updated_at = 23;
|
||||||
optional UsernameColor username_color = 24;
|
optional UsernameColor username_color = 24;
|
||||||
|
repeated ProfileLink links = 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountContact represents a contact method for an account
|
// AccountContact represents a contact method for an account
|
||||||
|
|||||||
@@ -3,3 +3,10 @@ http://localhost:3001 {
|
|||||||
header_up X-SiteName "ciallo"
|
header_up X-SiteName "ciallo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
http://localhost:3002 {
|
||||||
|
reverse_proxy /drive/* localhost:5001
|
||||||
|
reverse_proxy localhost:8007 {
|
||||||
|
header_up X-SiteName "regular"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Markdig" Version="0.43.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
302
DysonNetwork.Zone/Pages/About.cshtml
Normal file
302
DysonNetwork.Zone/Pages/About.cshtml
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
@page
|
||||||
|
@using NodaTime.Serialization.Protobuf
|
||||||
|
@model AboutModel
|
||||||
|
|
||||||
|
@{
|
||||||
|
Layout = "_LayoutContained";
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Model.UserAccount != null)
|
||||||
|
{
|
||||||
|
@if (!string.IsNullOrEmpty(Model.UserBackgroundUrl))
|
||||||
|
{
|
||||||
|
<img src="@Model.UserBackgroundUrl" class="w-full max-h-48 object-cover mb-8" alt="Background Image"/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mx-auto px-8 pb-8">
|
||||||
|
<h2 class="text-xl flex gap-2 mb-5 px-3">
|
||||||
|
<span class="mdi mdi-account-circle"></span>
|
||||||
|
About me
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-6 mb-8">
|
||||||
|
@if (!string.IsNullOrEmpty(Model.UserPictureUrl))
|
||||||
|
{
|
||||||
|
<img src="@Model.UserPictureUrl" class="w-20 h-20 rounded-full object-cover" alt="Avatar"/>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="w-20 h-20 rounded-full bg-gray-300 flex items-center justify-center">
|
||||||
|
<span class="text-2xl">@(Model.UserAccount.Name != null ? Model.UserAccount.Name[0] : '?')</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div>
|
||||||
|
<div class="text-2xl font-bold">
|
||||||
|
@(Model.UserAccount.Nick ?? Model.UserAccount.Name)
|
||||||
|
</div>
|
||||||
|
<div class="text-body-2 text-base-content/60">@@@Model.UserAccount.Name</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-6">
|
||||||
|
<div class="flex flex-col gap-4 flex-1">
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title"><span class="mdi mdi-home"></span> Info</h3>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.TimeZone))
|
||||||
|
{
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="flex gap-2">
|
||||||
|
<span class="mdi mdi-map-clock"></span>
|
||||||
|
Time Zone
|
||||||
|
</span>
|
||||||
|
<span class="text-right">
|
||||||
|
@Model.GetCurrentTimeInTimeZone(Model.UserAccount.Profile.TimeZone)
|
||||||
|
<span class="text-base-content/50">·</span>
|
||||||
|
@Model.GetOffsetUtcString(Model.UserAccount.Profile.TimeZone)
|
||||||
|
<span class="text-base-content/50">·</span>
|
||||||
|
@Model.UserAccount.Profile.TimeZone
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Location))
|
||||||
|
{
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="flex gap-2">
|
||||||
|
<span class="mdi mdi-map-marker"></span> Location
|
||||||
|
</span>
|
||||||
|
<span>@Model.UserAccount.Profile.Location</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.FirstName) || !string.IsNullOrEmpty(Model.UserAccount.Profile?.LastName))
|
||||||
|
{
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="flex gap-2">
|
||||||
|
<span class="mdi mdi-card-bulleted-outline"></span> Name
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
@string.Join(" ", new[]
|
||||||
|
{
|
||||||
|
Model.UserAccount.Profile.FirstName,
|
||||||
|
Model.UserAccount.Profile.MiddleName,
|
||||||
|
Model.UserAccount.Profile.LastName
|
||||||
|
}.Where(s => !string.IsNullOrEmpty(s)))
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Gender))
|
||||||
|
{
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="flex gap-2">
|
||||||
|
<span class="mdi mdi-human-non-binary"></span> Gender
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
@Model.UserAccount.Profile.Gender
|
||||||
|
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Pronouns))
|
||||||
|
{
|
||||||
|
<span class="text-base-content/50">·</span>
|
||||||
|
@Model.UserAccount.Profile.Pronouns
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="flex gap-2">
|
||||||
|
<span class="mdi mdi-history"></span> Joined at
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
@Model.UserAccount.CreatedAt?.ToDateTimeOffset().ToString("MMMM d, yyyy")
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@if (Model.UserAccount.Profile?.Birthday != null)
|
||||||
|
{
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="flex gap-2">
|
||||||
|
<span class="mdi mdi-cake-variant"></span> Birthday
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
@Model.CalculateAge(Model.UserAccount.Profile.Birthday.ToInstant()) yrs old
|
||||||
|
<span class="text-base-content/50">·</span>
|
||||||
|
@Model.UserAccount.Profile.Birthday?.ToDateTimeOffset().ToString("MMMM d")
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model.UserAccount.PerkSubscription != null)
|
||||||
|
{
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="text-xl font-bold">
|
||||||
|
@Model.GetPerkInfo(Model.UserAccount.PerkSubscription.Identifier).Name Tier
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-base-content/70">Stellar Program Member</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-4xl"
|
||||||
|
style="color: @Model.GetPerkInfo(Model.UserAccount.PerkSubscription.Identifier).Color">
|
||||||
|
★
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex justify-between mb-2">
|
||||||
|
<div>Level @Model.UserAccount.Profile?.Level</div>
|
||||||
|
<div>@Model.UserAccount.Profile?.Experience XP</div>
|
||||||
|
</div>
|
||||||
|
<progress class="progress progress-success"
|
||||||
|
value="@(Model.UserAccount.Profile?.LevelingProgress ?? 0)" max="1"></progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 flex-1">
|
||||||
|
@if (Model.UserAccount.Profile?.Links.Count > 0)
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">
|
||||||
|
<span class="mdi mdi-link-variant"></span> Links
|
||||||
|
</h3>
|
||||||
|
<ul class="list pt-2">
|
||||||
|
@foreach (var link in Model.UserAccount.Profile.Links)
|
||||||
|
{
|
||||||
|
<li class="list-row p-0">
|
||||||
|
<div class="list-col-grow">
|
||||||
|
<div>@(link.Name)</div>
|
||||||
|
<div class="text-xs font-semibold opacity-60">@(link.Url)</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-square btn-ghost" href="@link.Url" target="_blank">
|
||||||
|
<span class="mdi mdi-launch"></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Model.UserAccount.Contacts.Count > 0)
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">
|
||||||
|
<span class="mdi mdi-contacts"></span> Contacts
|
||||||
|
</h3>
|
||||||
|
<ul class="list pt-2">
|
||||||
|
@foreach (var contact in Model.UserAccount.Contacts)
|
||||||
|
{
|
||||||
|
<li class="list-row p-0" x-data="{ content: '@contact.Content' }">
|
||||||
|
<div class="list-col-grow">
|
||||||
|
<div>@(contact.Content)</div>
|
||||||
|
<div
|
||||||
|
class="text-xs font-semibold opacity-60">@(contact.Type.ToString())</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-square btn-ghost" x-clipboard="content">
|
||||||
|
<span class="mdi mdi-content-copy"></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Model.HtmlBio))
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">
|
||||||
|
<span class="mdi mdi-information"></span> Bio
|
||||||
|
</h3>
|
||||||
|
<div class="prose prose-sm max-w-none">
|
||||||
|
@Html.Raw(Model.HtmlBio)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Model.Site != null)
|
||||||
|
{
|
||||||
|
<div class="container mx-auto px-8 pb-8">
|
||||||
|
<h2 class="text-xl flex gap-2 mb-5 px-3">
|
||||||
|
<span class="mdi mdi-sitemap"></span>
|
||||||
|
About the site
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-6">
|
||||||
|
<div class="flex flex-col gap-4 flex-1">
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title"><span class="mdi mdi-home"></span> Info</h3>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="flex gap-2">
|
||||||
|
<span class="mdi mdi-label"></span>
|
||||||
|
Name
|
||||||
|
</span>
|
||||||
|
<span class="text-right">@Model.Site.Name</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="flex gap-2">
|
||||||
|
<span class="mdi mdi-tag"></span>
|
||||||
|
Slug
|
||||||
|
</span>
|
||||||
|
<span class="text-right font-mono">@Model.Site.Slug</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 flex-1">
|
||||||
|
@if (!string.IsNullOrEmpty(Model.Site.Description))
|
||||||
|
{
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">
|
||||||
|
<span class="mdi mdi-information"></span> Description
|
||||||
|
</h3>
|
||||||
|
<div class="prose prose-sm max-w-none">
|
||||||
|
@Model.Site.Description
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex flex-col p-5 opacity-80 text-sm">
|
||||||
|
<p>Proudly powered by the Solar Network Pages</p>
|
||||||
|
<p>Hosted on the Solar Network</p>
|
||||||
|
<p class="mt-2">Networking with Cloudflare</p>
|
||||||
|
<p>Therefore, if the site is down, 99% is Cloudflare's fault</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
89
DysonNetwork.Zone/Pages/About.cshtml.cs
Normal file
89
DysonNetwork.Zone/Pages/About.cshtml.cs
Normal file
@@ -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")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
@page
|
@page
|
||||||
@model IndexModel
|
@model IndexModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
|
Layout = "_LayoutContained";
|
||||||
ViewData["Title"] = "Solar Network Pages";
|
ViewData["Title"] = "Solar Network Pages";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="h-screen flex justify-center items-center">
|
<div class="h-full flex justify-center items-center">
|
||||||
<div class="text-center max-w-96">
|
<div class="text-center max-w-96">
|
||||||
<img src="~/favicon.png" width="80" height="80" alt="Logo" class="mb-1 mx-auto"/>
|
<img src="~/favicon.png" width="80" height="80" alt="Logo" class="mb-1 mx-auto"/>
|
||||||
<h1 class="text-2xl">Hello World 👋</h1>
|
<h1 class="text-2xl">Hello World 👋</h1>
|
||||||
|
|||||||
62
DysonNetwork.Zone/Pages/Posts.cshtml
Normal file
62
DysonNetwork.Zone/Pages/Posts.cshtml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
@page
|
||||||
|
@model DysonNetwork.Zone.Pages.PostsModel
|
||||||
|
@using DysonNetwork.Shared.Models
|
||||||
|
@{
|
||||||
|
Layout = "_LayoutContained";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mx-auto px-8 pb-8">
|
||||||
|
<h1 class="text-3xl font-bold mb-8">Posts</h1>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="w-full md:w-2/3">
|
||||||
|
@if (Model.Posts.Any())
|
||||||
|
{
|
||||||
|
<div class="space-y-8">
|
||||||
|
@foreach (var post in Model.Posts)
|
||||||
|
{
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">@post.Title</h2>
|
||||||
|
<p>@post.Content</p>
|
||||||
|
<div class="card-actions justify-end">
|
||||||
|
<div class="text-sm text-base-content/60">
|
||||||
|
Posted on @post.CreatedAt.ToDateTimeOffset().ToString("yyyy-MM-dd")
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="text-center py-16">
|
||||||
|
<p class="text-lg">No posts found.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Model.TotalPages > 1)
|
||||||
|
{
|
||||||
|
<div class="flex justify-center mt-8">
|
||||||
|
<div class="btn-group">
|
||||||
|
@for (var i = 1; i <= Model.TotalPages; i++)
|
||||||
|
{
|
||||||
|
<a href="/posts?currentPage=@i" class="btn @(i == Model.CurrentPage ? "btn-active" : "")">@i</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full md:w-1/3">
|
||||||
|
<div class="card bg-base-100 border">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">Publisher Info</h3>
|
||||||
|
<p>This is where publisher information will be displayed.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
48
DysonNetwork.Zone/Pages/Posts.cshtml.cs
Normal file
48
DysonNetwork.Zone/Pages/Posts.cshtml.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Zone.Pages
|
||||||
|
{
|
||||||
|
public class PostsModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly PostService.PostServiceClient _postClient;
|
||||||
|
|
||||||
|
public PostsModel(PostService.PostServiceClient postClient)
|
||||||
|
{
|
||||||
|
_postClient = postClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SnPost> Posts { get; set; } = new();
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
public int CurrentPage { get; set; }
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
public int TotalPages => (int)System.Math.Ceiling(TotalCount / (double)PageSize);
|
||||||
|
|
||||||
|
public async Task OnGetAsync(int currentPage = 1)
|
||||||
|
{
|
||||||
|
CurrentPage = currentPage;
|
||||||
|
|
||||||
|
var request = new ListPostsRequest
|
||||||
|
{
|
||||||
|
OrderBy = "date",
|
||||||
|
OrderDesc = true,
|
||||||
|
PageSize = PageSize,
|
||||||
|
PageToken = ((CurrentPage - 1) * PageSize).ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await _postClient.ListPostsAsync(request);
|
||||||
|
|
||||||
|
if (response?.Posts != null)
|
||||||
|
{
|
||||||
|
Posts = response.Posts.Select(SnPost.FromProtoValue).ToList();
|
||||||
|
TotalCount = response.TotalSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,15 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>@ViewData["Title"]</title>
|
<title>@ViewData["Title"]</title>
|
||||||
<script type="importmap"></script>
|
<script type="importmap"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@@ryangjchandler/alpine-clipboard@2.x.x/dist/alpine-clipboard.js" defer></script>
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@@mdi/font@7.4.47/css/materialdesignicons.min.css" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="~/css/site.dist.css" asp-append-version="true"/>
|
<link rel="stylesheet" href="~/css/site.dist.css" asp-append-version="true"/>
|
||||||
<link rel="stylesheet" href="~/DysonNetwork.Zone.styles.css" asp-append-version="true"/>
|
<link rel="stylesheet" href="~/DysonNetwork.Zone.styles.css" asp-append-version="true"/>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="~/favicon.png" />
|
<link rel="icon" type="image/png" href="~/favicon.png" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
38
DysonNetwork.Zone/Pages/Shared/_LayoutContained.cshtml
Normal file
38
DysonNetwork.Zone/Pages/Shared/_LayoutContained.cshtml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
@using DysonNetwork.Zone.Publication
|
||||||
|
@using DysonNetwork.Shared.Models
|
||||||
|
@{
|
||||||
|
Layout = "_Layout";
|
||||||
|
var site = Context.Items[PublicationSiteMiddleware.SiteContextKey] as SnPublicationSite;
|
||||||
|
var siteDisplayName = site?.Name ?? "Solar Network";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="navbar backdrop-blur-md bg-white/1 shadow-xl px-5">
|
||||||
|
<div class="flex-1">
|
||||||
|
<a class="btn btn-ghost text-xl" asp-page="/Index">@siteDisplayName</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<ul class="menu menu-horizontal px-1">
|
||||||
|
<li><a asp-page="/Posts">Posts</a></li>
|
||||||
|
<li><a asp-page="/About">About</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="content-main">
|
||||||
|
@RenderBody()
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.navbar {
|
||||||
|
height: 64px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.content-main {
|
||||||
|
height: calc(100vh - 64px);
|
||||||
|
margin-top: 64px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,6 +26,8 @@ builder.Services.AddRingService();
|
|||||||
builder.Services.AddAccountService();
|
builder.Services.AddAccountService();
|
||||||
builder.Services.AddSphereService();
|
builder.Services.AddSphereService();
|
||||||
|
|
||||||
|
builder.Services.Configure<RouteOptions>(options => { options.LowercaseUrls = true; });
|
||||||
|
|
||||||
builder.AddSwaggerManifest(
|
builder.AddSwaggerManifest(
|
||||||
"DysonNetwork.Zone",
|
"DysonNetwork.Zone",
|
||||||
"The zone service in the Solar Network."
|
"The zone service in the Solar Network."
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"name": "dyson-zone",
|
"name": "dyson-zone",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/cli": "^4.1.17",
|
"@tailwindcss/cli": "^4.1.17",
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"daisyui": "^5.5.5",
|
"daisyui": "^5.5.5",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
@@ -84,6 +85,8 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],
|
"@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
||||||
@@ -94,6 +97,8 @@
|
|||||||
|
|
||||||
"bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],
|
"bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],
|
||||||
|
|
||||||
|
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||||
|
|
||||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
"daisyui": ["daisyui@5.5.5", "", {}, "sha512-ekvI93ZkWIJoCOtDl0D2QMxnWvTejk9V5nWBqRv+7t0xjiBXqAK5U6o6JE2RPvlIC3EqwNyUoIZSdHX9MZK3nw=="],
|
"daisyui": ["daisyui@5.5.5", "", {}, "sha512-ekvI93ZkWIJoCOtDl0D2QMxnWvTejk9V5nWBqRv+7t0xjiBXqAK5U6o6JE2RPvlIC3EqwNyUoIZSdHX9MZK3nw=="],
|
||||||
@@ -150,6 +155,8 @@
|
|||||||
|
|
||||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
|
"postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
|
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
|
||||||
@@ -162,6 +169,8 @@
|
|||||||
|
|
||||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/cli": "^4.1.17",
|
"@tailwindcss/cli": "^4.1.17",
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"daisyui": "^5.5.5",
|
"daisyui": "^5.5.5",
|
||||||
"tailwindcss": "^4.1.17"
|
"tailwindcss": "^4.1.17"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
|
||||||
|
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@plugin "daisyui";
|
@plugin "daisyui";
|
||||||
|
@plugin "@tailwindcss/typography";
|
||||||
|
|
||||||
@layer theme, base, components, utilities;
|
@layer theme, base, components, utilities;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user