diff --git a/DysonNetwork.Shared/Models/PublicationSite.cs b/DysonNetwork.Shared/Models/PublicationSite.cs index 98a42f3..646ef73 100644 --- a/DysonNetwork.Shared/Models/PublicationSite.cs +++ b/DysonNetwork.Shared/Models/PublicationSite.cs @@ -20,6 +20,19 @@ public enum PublicationSiteMode SelfManaged } +public class PublicationSiteConfig +{ + public string? StyleOverride { get; set; } + public List? NavItems { get; set; } = []; +} + +public class PublicationSiteNavItem +{ + [MaxLength(1024)] public string Label { get; set; } = null!; + [MaxLength(8192)] public string Href { get; set; } = null!; + Dictionary Attributes { get; set; } = new(); +} + public class SnPublicationSite : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); @@ -29,6 +42,7 @@ public class SnPublicationSite : ModelBase public PublicationSiteMode Mode { get; set; } = PublicationSiteMode.FullyManaged; public List Pages { get; set; } = []; + [Column(TypeName = "jsonb")] public PublicationSiteConfig Config { get; set; } = new(); public Guid PublisherId { get; set; } [NotMapped] public SnPublisher Publisher { get; set; } = null!; diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs index cbb58c2..1cdfec2 100644 --- a/DysonNetwork.Sphere/Post/PostController.cs +++ b/DysonNetwork.Sphere/Post/PostController.cs @@ -40,26 +40,6 @@ public class PostController( return Ok(posts); } - /// - /// Retrieves a paginated list of posts with optional filtering and sorting. - /// - /// Whether to include reply posts in the results. If false, only root posts are returned. - /// The number of posts to skip for pagination. - /// The maximum number of posts to return (default: 20). - /// Filter posts by publisher name. - /// Filter posts by realm slug. - /// Filter posts by post type (as integer). - /// Filter posts by category slugs. - /// Filter posts by tag slugs. - /// Search term to filter posts by title, description, or content. - /// If true, only returns posts that have attachments. - /// If true, returns posts in random order. If false, orders by published/created date (newest first). - /// If true, returns posts that pinned. If false, returns posts that are not pinned. If null, returns all posts. - /// - /// Returns an ActionResult containing a list of Post objects that match the specified criteria. - /// Includes an X-Total header with the total count of matching posts before pagination. - /// - /// Returns the list of posts matching the criteria. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] [ProducesResponseType(StatusCodes.Status400BadRequest)] diff --git a/DysonNetwork.Zone/Migrations/20251210104942_AddSiteGlobalConfig.Designer.cs b/DysonNetwork.Zone/Migrations/20251210104942_AddSiteGlobalConfig.Designer.cs new file mode 100644 index 0000000..2e1c275 --- /dev/null +++ b/DysonNetwork.Zone/Migrations/20251210104942_AddSiteGlobalConfig.Designer.cs @@ -0,0 +1,156 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Shared.Models; +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("20251210104942_AddSiteGlobalConfig")] + partial class AddSiteGlobalConfig + { + /// + 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("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("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/20251210104942_AddSiteGlobalConfig.cs b/DysonNetwork.Zone/Migrations/20251210104942_AddSiteGlobalConfig.cs new file mode 100644 index 0000000..430275d --- /dev/null +++ b/DysonNetwork.Zone/Migrations/20251210104942_AddSiteGlobalConfig.cs @@ -0,0 +1,30 @@ +using DysonNetwork.Shared.Models; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DysonNetwork.Zone.Migrations +{ + /// + public partial class AddSiteGlobalConfig : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "config", + table: "publication_sites", + type: "jsonb", + nullable: false, + defaultValue: new PublicationSiteConfig()); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "config", + table: "publication_sites"); + } + } +} diff --git a/DysonNetwork.Zone/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Zone/Migrations/AppDatabaseModelSnapshot.cs index 1ce3f91..c8d6308 100644 --- a/DysonNetwork.Zone/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Zone/Migrations/AppDatabaseModelSnapshot.cs @@ -1,6 +1,7 @@ // using System; using System.Collections.Generic; +using DysonNetwork.Shared.Models; using DysonNetwork.Zone; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -82,6 +83,11 @@ namespace DysonNetwork.Zone.Migrations .HasColumnType("uuid") .HasColumnName("account_id"); + b.Property("Config") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("config"); + b.Property("CreatedAt") .HasColumnType("timestamp with time zone") .HasColumnName("created_at"); diff --git a/DysonNetwork.Zone/Publication/PublicationSiteController.cs b/DysonNetwork.Zone/Publication/PublicationSiteController.cs index 449272e..1c7d57b 100644 --- a/DysonNetwork.Zone/Publication/PublicationSiteController.cs +++ b/DysonNetwork.Zone/Publication/PublicationSiteController.cs @@ -59,7 +59,8 @@ public class PublicationSiteController( [HttpPost("{pubName}")] [Authorize] - public async Task> CreateSite([FromRoute] string pubName, [FromBody] PublicationSiteRequest request) + public async Task> CreateSite([FromRoute] string pubName, + [FromBody] PublicationSiteRequest request) { if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); @@ -75,6 +76,7 @@ public class PublicationSiteController( Name = request.Name, Description = request.Description, PublisherId = publisher.Id, + Config = request.Config ?? new PublicationSiteConfig(), AccountId = accountId }; @@ -96,7 +98,8 @@ public class PublicationSiteController( [HttpPatch("{pubName}/{slug}")] [Authorize] - public async Task> UpdateSite([FromRoute] string pubName, string slug, [FromBody] PublicationSiteRequest request) + public async Task> UpdateSite([FromRoute] string pubName, string slug, + [FromBody] PublicationSiteRequest request) { if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); @@ -113,6 +116,7 @@ public class PublicationSiteController( site.Slug = request.Slug; site.Name = request.Name; site.Description = request.Description ?? site.Description; + site.Config = request.Config ?? site.Config; try { @@ -153,18 +157,10 @@ public class PublicationSiteController( return NoContent(); } - [HttpGet("site/{slug}/page")] - public async Task> RenderPage(string slug, [FromQuery] string path = "/") - { - var page = await publicationService.RenderPage(slug, path); - if (page == null) - return NotFound(); - return Ok(page); - } - [HttpGet("{pubName}/{siteSlug}/pages")] [Authorize] - public async Task>> ListPagesForSite([FromRoute] string pubName, [FromRoute] string siteSlug) + public async Task>> ListPagesForSite([FromRoute] string pubName, + [FromRoute] string siteSlug) { var site = await publicationService.GetSiteBySlug(siteSlug); if (site == null) return NotFound(); @@ -187,7 +183,8 @@ public class PublicationSiteController( [HttpPost("{pubName}/{siteSlug}/pages")] [Authorize] - public async Task> CreatePage([FromRoute] string pubName, [FromRoute] string siteSlug, [FromBody] PublicationPageRequest request) + public async Task> CreatePage([FromRoute] string pubName, + [FromRoute] string siteSlug, [FromBody] PublicationPageRequest request) { if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); @@ -280,6 +277,7 @@ public class PublicationSiteController( [MaxLength(4096)] public string Slug { get; set; } = null!; [MaxLength(4096)] public string Name { get; set; } = null!; [MaxLength(8192)] public string? Description { get; set; } + public PublicationSiteConfig? Config { get; set; } } public class PublicationPageRequest @@ -288,4 +286,4 @@ public class PublicationSiteController( [MaxLength(8192)] public string? Path { get; set; } public Dictionary? Config { get; set; } } -} +} \ No newline at end of file