From afccb27bd437b3e21b5fa79230ebd8a16023d1fe Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 20 Nov 2025 22:40:36 +0800 Subject: [PATCH] :sparkles: Site mode --- DysonNetwork.Shared/Models/PublicationSite.cs | 42 +++-- DysonNetwork.Zone/.gitignore | 2 + .../20251120134237_AddSiteMode.Designer.cs | 150 ++++++++++++++++++ .../Migrations/20251120134237_AddSiteMode.cs | 52 ++++++ .../Migrations/AppDatabaseModelSnapshot.cs | 14 +- .../Publication/PublicationSiteController.cs | 21 ++- .../Publication/PublicationSiteService.cs | 17 +- DysonNetwork.Zone/appsettings.json | 3 + 8 files changed, 269 insertions(+), 32 deletions(-) create mode 100644 DysonNetwork.Zone/Migrations/20251120134237_AddSiteMode.Designer.cs create mode 100644 DysonNetwork.Zone/Migrations/20251120134237_AddSiteMode.cs diff --git a/DysonNetwork.Shared/Models/PublicationSite.cs b/DysonNetwork.Shared/Models/PublicationSite.cs index 8445b9a..98a42f3 100644 --- a/DysonNetwork.Shared/Models/PublicationSite.cs +++ b/DysonNetwork.Shared/Models/PublicationSite.cs @@ -1,9 +1,25 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using Google.Api; namespace DysonNetwork.Shared.Models; +public enum PublicationSiteMode +{ + /// + /// The fully managed mode which allow other server to handle all the requests + /// Including the rendering, data fetching etc. /// Very easy to use and efficient. + /// + FullyManaged, + + /// + /// The self-managed mode allows user to upload their own site assets and use the Solar Network Pages + /// as a static site hosting platform. + /// + SelfManaged +} + public class SnPublicationSite : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); @@ -11,34 +27,36 @@ public class SnPublicationSite : ModelBase [MaxLength(4096)] public string Name { get; set; } = null!; [MaxLength(8192)] public string? Description { get; set; } + public PublicationSiteMode Mode { get; set; } = PublicationSiteMode.FullyManaged; public List Pages { get; set; } = []; - + public Guid PublisherId { get; set; } [NotMapped] public SnPublisher Publisher { get; set; } = null!; public Guid AccountId { get; set; } - // Preloaded via the remote services [NotMapped] public SnAccount? Account { get; set; } } -public abstract class PublicationPagePresets +public enum PublicationPageType { - // Will told the Isolated Island to render according to prebuilt pages by us - public const string Landing = "landing"; // Some kind of the mixed version of the profile and the posts - public const string Profile = "profile"; - public const string Posts = "posts"; - - // Will told the Isolated Island to render according to the blocks to use custom stuff - public const string Custom = "custom"; + /// + /// The HTML page type allows user adding a custom HTML page in the fully managed mode. + /// + HtmlPage, + /// + /// The redirect mode allows user to create a shortcut for their own link. + /// such as example.solian.page/rickroll -- DyZ 301 -> youtube.com/... + /// + Redirect } public class SnPublicationPage : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(8192)] public string Preset { get; set; } = PublicationPagePresets.Landing; + public PublicationPageType Type { get; set; } = PublicationPageType.HtmlPage; [MaxLength(8192)] public string Path { get; set; } = "/"; [Column(TypeName = "jsonb")] public Dictionary Config { get; set; } = new(); - + public Guid SiteId { get; set; } [JsonIgnore] public SnPublicationSite Site { get; set; } = null!; } \ No newline at end of file diff --git a/DysonNetwork.Zone/.gitignore b/DysonNetwork.Zone/.gitignore index 542867e..1d8c562 100644 --- a/DysonNetwork.Zone/.gitignore +++ b/DysonNetwork.Zone/.gitignore @@ -33,3 +33,5 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store + +SiteData diff --git a/DysonNetwork.Zone/Migrations/20251120134237_AddSiteMode.Designer.cs b/DysonNetwork.Zone/Migrations/20251120134237_AddSiteMode.Designer.cs new file mode 100644 index 0000000..43c5b1c --- /dev/null +++ b/DysonNetwork.Zone/Migrations/20251120134237_AddSiteMode.Designer.cs @@ -0,0 +1,150 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Zone; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Zone.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20251120134237_AddSiteMode")] + partial class AddSiteMode + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublicationPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property>("Config") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("config"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("path"); + + b.Property("SiteId") + .HasColumnType("uuid") + .HasColumnName("site_id"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_publication_pages"); + + b.HasIndex("SiteId") + .HasDatabaseName("ix_publication_pages_site_id"); + + b.ToTable("publication_pages", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublicationSite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("description"); + + b.Property("Mode") + .HasColumnType("integer") + .HasColumnName("mode"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("name"); + + b.Property("PublisherId") + .HasColumnType("uuid") + .HasColumnName("publisher_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("slug"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_publication_sites"); + + b.ToTable("publication_sites", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublicationPage", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPublicationSite", "Site") + .WithMany("Pages") + .HasForeignKey("SiteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_publication_pages_publication_sites_site_id"); + + b.Navigation("Site"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPublicationSite", b => + { + b.Navigation("Pages"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Zone/Migrations/20251120134237_AddSiteMode.cs b/DysonNetwork.Zone/Migrations/20251120134237_AddSiteMode.cs new file mode 100644 index 0000000..3c4c111 --- /dev/null +++ b/DysonNetwork.Zone/Migrations/20251120134237_AddSiteMode.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DysonNetwork.Zone.Migrations +{ + /// + public partial class AddSiteMode : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "preset", + table: "publication_pages"); + + migrationBuilder.AddColumn( + name: "mode", + table: "publication_sites", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "type", + table: "publication_pages", + type: "integer", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "mode", + table: "publication_sites"); + + migrationBuilder.DropColumn( + name: "type", + table: "publication_pages"); + + migrationBuilder.AddColumn( + name: "preset", + table: "publication_pages", + type: "character varying(8192)", + maxLength: 8192, + nullable: false, + defaultValue: ""); + } + } +} diff --git a/DysonNetwork.Zone/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Zone/Migrations/AppDatabaseModelSnapshot.cs index 1f448c4..1ce3f91 100644 --- a/DysonNetwork.Zone/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Zone/Migrations/AppDatabaseModelSnapshot.cs @@ -50,16 +50,14 @@ namespace DysonNetwork.Zone.Migrations .HasColumnType("character varying(8192)") .HasColumnName("path"); - b.Property("Preset") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("preset"); - b.Property("SiteId") .HasColumnType("uuid") .HasColumnName("site_id"); + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone") .HasColumnName("updated_at"); @@ -97,6 +95,10 @@ namespace DysonNetwork.Zone.Migrations .HasColumnType("character varying(8192)") .HasColumnName("description"); + b.Property("Mode") + .HasColumnType("integer") + .HasColumnName("mode"); + b.Property("Name") .IsRequired() .HasMaxLength(4096) diff --git a/DysonNetwork.Zone/Publication/PublicationSiteController.cs b/DysonNetwork.Zone/Publication/PublicationSiteController.cs index 1f59b37..5d430b2 100644 --- a/DysonNetwork.Zone/Publication/PublicationSiteController.cs +++ b/DysonNetwork.Zone/Publication/PublicationSiteController.cs @@ -4,7 +4,6 @@ using DysonNetwork.Shared.Registry; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Models = DysonNetwork.Shared.Models; -using PublicationPagePresets = DysonNetwork.Shared.Models.PublicationPagePresets; namespace DysonNetwork.Zone.Publication; @@ -15,10 +14,10 @@ public class PublicationSiteController( RemotePublisherService publisherService ) : ControllerBase { - [HttpGet("site/{slug}")] - public async Task> GetSite(string slug) + [HttpGet("{pubName}/{slug}")] + public async Task> GetSite(string pubName, string slug) { - var site = await publicationService.GetSiteBySlug(slug); + var site = await publicationService.GetSiteBySlug(slug, pubName); if (site == null) return NotFound(); return Ok(site); @@ -71,6 +70,7 @@ public class PublicationSiteController( var site = new SnPublicationSite { + Mode = request.Mode, Slug = request.Slug, Name = request.Name, Description = request.Description, @@ -106,12 +106,10 @@ public class PublicationSiteController( if (publisher == null) return NotFound(); var site = await publicationService.GetSiteById(id); - if (site == null) - return NotFound(); - - if (site.PublisherId != publisher.Id) + if (site == null || site.PublisherId != publisher.Id) return NotFound(); + site.Mode = request.Mode; site.Slug = request.Slug; site.Name = request.Name; site.Description = request.Description ?? site.Description; @@ -204,7 +202,7 @@ public class PublicationSiteController( var page = new SnPublicationPage { - Preset = request.Preset ?? PublicationPagePresets.Landing, + Type = request.Type, Path = request.Path ?? "/", Config = request.Config ?? new Dictionary(), SiteId = site.Id @@ -239,7 +237,7 @@ public class PublicationSiteController( var accountId = Guid.Parse(currentUser.Id); - if (request.Preset != null) page.Preset = request.Preset; + page.Type = request.Type; if (request.Path != null) page.Path = request.Path; if (request.Config != null) page.Config = request.Config; @@ -278,6 +276,7 @@ public class PublicationSiteController( public class PublicationSiteRequest { + public PublicationSiteMode Mode { get; set; } [MaxLength(4096)] public string Slug { get; set; } = null!; [MaxLength(4096)] public string Name { get; set; } = null!; [MaxLength(8192)] public string? Description { get; set; } @@ -285,7 +284,7 @@ public class PublicationSiteController( public class PublicationPageRequest { - [MaxLength(8192)] public string? Preset { get; set; } + public PublicationPageType Type { get; set; } [MaxLength(8192)] public string? Path { get; set; } public Dictionary? Config { get; set; } } diff --git a/DysonNetwork.Zone/Publication/PublicationSiteService.cs b/DysonNetwork.Zone/Publication/PublicationSiteService.cs index 3231fb5..b172e0d 100644 --- a/DysonNetwork.Zone/Publication/PublicationSiteService.cs +++ b/DysonNetwork.Zone/Publication/PublicationSiteService.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; @@ -9,7 +10,8 @@ namespace DysonNetwork.Zone.Publication; public class PublicationSiteService( AppDatabase db, RemotePublisherService publisherService, - RemoteAccountService remoteAccounts + RemoteAccountService remoteAccounts, + RemotePublisherService remotePublishers ) { public async Task GetSiteById(Guid id) @@ -19,9 +21,18 @@ public class PublicationSiteService( .FirstOrDefaultAsync(s => s.Id == id); } - public async Task GetSiteBySlug(string slug) + public async Task GetSiteBySlug(string slug, string? pubName = null) { + Guid? pubId = null; + if (pubName != null) + { + var pub = await remotePublishers.GetPublisherByName(pubName); + if (pub == null) throw new InvalidOperationException("Publisher not found."); + pubId = pub.Id; + } + return await db.PublicationSites + .If(pubId.HasValue, q => q.Where(s => s.PublisherId == pubId.Value)) .Include(s => s.Pages) .FirstOrDefaultAsync(s => s.Slug == slug); } @@ -39,7 +50,7 @@ public class PublicationSiteService( var perk = (await remoteAccounts.GetAccount(accountId)).PerkSubscription; var perkLevel = perk is not null ? PerkSubscriptionPrivilege.GetPrivilegeFromIdentifier(perk.Identifier) : 0; - var maxSite = (perkLevel) switch + var maxSite = perkLevel switch { 1 => 2, 2 => 3, diff --git a/DysonNetwork.Zone/appsettings.json b/DysonNetwork.Zone/appsettings.json index e9d6c80..f669dd6 100644 --- a/DysonNetwork.Zone/appsettings.json +++ b/DysonNetwork.Zone/appsettings.json @@ -13,5 +13,8 @@ "KnownProxies": ["127.0.0.1", "::1"], "Swagger": { "PublicBasePath": "/zone" + }, + "Sites": { + "BasePath": "/SiteData" } }