diff --git a/DysonNetwork.Shared/Proto/post.proto b/DysonNetwork.Shared/Proto/post.proto index fa94f72..a937b8d 100644 --- a/DysonNetwork.Shared/Proto/post.proto +++ b/DysonNetwork.Shared/Proto/post.proto @@ -211,7 +211,11 @@ message PostAward { // ==================================== message GetPostRequest { - string id = 1; + oneof identifier { + string id = 1; + string slug = 2; + } + google.protobuf.StringValue publisher_id = 3; } message GetPostBatchRequest { diff --git a/DysonNetwork.Sphere/Post/PostServiceGrpc.cs b/DysonNetwork.Sphere/Post/PostServiceGrpc.cs index f7153f8..d6ee42e 100644 --- a/DysonNetwork.Sphere/Post/PostServiceGrpc.cs +++ b/DysonNetwork.Sphere/Post/PostServiceGrpc.cs @@ -10,10 +10,26 @@ public class PostServiceGrpc(AppDatabase db, PostService ps) : Shared.Proto.Post { public override async Task GetPost(GetPostRequest request, ServerCallContext context) { - if (!Guid.TryParse(request.Id, out var id)) - throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid post id")); + var postQuery = db.Posts.AsQueryable(); - var post = await db.Posts + switch (request.IdentifierCase) + { + case GetPostRequest.IdentifierOneofCase.Id: + if (!Guid.TryParse(request.Id, out var id)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid post id")); + postQuery = postQuery.Where(p => p.Id == id); + break; + case GetPostRequest.IdentifierOneofCase.Slug: + postQuery = postQuery.Where(p => p.Slug == request.Slug); + break; + default: + throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid identifier case")); + } + + if (!string.IsNullOrWhiteSpace(request.PublisherId) && Guid.TryParse(request.PublisherId, out var pid)) + postQuery = postQuery.Where(p => p.PublisherId == pid); + + var post = await postQuery .Include(p => p.Publisher) .Include(p => p.Tags) .Include(p => p.Categories) @@ -21,7 +37,7 @@ public class PostServiceGrpc(AppDatabase db, PostService ps) : Shared.Proto.Post .Include(p => p.ForwardedPost) .Include(p => p.FeaturedRecords) .FilterWithVisibility(null, [], []) - .FirstOrDefaultAsync(p => p.Id == id); + .FirstOrDefaultAsync(); if (post == null) throw new RpcException(new Status(StatusCode.NotFound, "post not found")); diff --git a/DysonNetwork.Zone/Pages/Posts.cshtml b/DysonNetwork.Zone/Pages/Posts.cshtml index 3b07b88..752a767 100644 --- a/DysonNetwork.Zone/Pages/Posts.cshtml +++ b/DysonNetwork.Zone/Pages/Posts.cshtml @@ -1,29 +1,105 @@ - @page @model DysonNetwork.Zone.Pages.PostsModel -@using DysonNetwork.Shared.Models @{ Layout = "_LayoutContained"; + const string defaultAvatar = "https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp"; } -
-

Posts

+
+

+ Posts +

-
-
+
+
@if (Model.Posts.Any()) {
@foreach (var post in Model.Posts) { -
-
-

@post.Title

-

@post.Content

-
+
+
+
+ @if (!string.IsNullOrWhiteSpace(post.Title) || !string.IsNullOrWhiteSpace(post.Description)) + { +
+ @if (!string.IsNullOrWhiteSpace(post.Title)) + { +

+ @post.Title +

+ } + @if (!string.IsNullOrWhiteSpace(post.Description)) + { +

@post.Description

+ } +
+ } +

@post.Content

+
+ + @if (post.Attachments.Any()) + { +
+ @foreach (var attachment in post.Attachments) + { +
+ @if (attachment.MimeType!.StartsWith("image")) + { +
+ @attachment.Name +
+ } + else if (attachment.MimeType!.StartsWith("video")) + { + + } + else if (attachment.MimeType!.StartsWith("audio")) + { + + } + else + { + @attachment.Name + } +
+ } +
+ } + + @if (post.Categories.Any() || post.Tags.Any()) + { +
+ @foreach (var category in post.Categories) + { + + + @(!string.IsNullOrEmpty(category.Name) ? category.Name : category.Slug) + + } + @foreach (var tag in post.Tags) + { + + + @(!string.IsNullOrEmpty(tag.Name) ? tag.Name : tag.Slug) + + } +
+ } + +
Posted on @post.CreatedAt.ToDateTimeOffset().ToString("yyyy-MM-dd")
+ + +
@@ -40,23 +116,54 @@ @if (Model.TotalPages > 1) {
-
- @for (var i = 1; i <= Model.TotalPages; i++) +
+ @for (var idx = 1; idx <= Model.TotalPages; idx++) { - @i + var pageIdx = idx; + + @pageIdx + }
}
-
-
-
-

Publisher Info

-

This is where publisher information will be displayed.

+ @if (Model.Publisher != null) + { +
+
+ @if (Model.Publisher!.Background != null) + { +
+ Background +
+ } +
+
+
+
+ Avatar +
+
+

+ @Model.Publisher.Nick + @@@Model.Publisher.Name +

+
+ @if (!string.IsNullOrWhiteSpace(Model.Publisher.Bio)) + { +

@Model.Publisher.Bio

+ } +
-
+ }
-
+
\ No newline at end of file diff --git a/DysonNetwork.Zone/Pages/Posts.cshtml.cs b/DysonNetwork.Zone/Pages/Posts.cshtml.cs index d67a914..b45985e 100644 --- a/DysonNetwork.Zone/Pages/Posts.cshtml.cs +++ b/DysonNetwork.Zone/Pages/Posts.cshtml.cs @@ -1,48 +1,44 @@ - -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; -using Microsoft.AspNetCore.Mvc; +using DysonNetwork.Shared.Registry; +using DysonNetwork.Zone.Publication; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace DysonNetwork.Zone.Pages +namespace DysonNetwork.Zone.Pages; + +public class PostsModel(PostService.PostServiceClient postClient, RemotePublisherService rps) : PageModel { - public class PostsModel : PageModel + public SnPublicationSite? Site { get; set; } + public SnPublisher? Publisher { get; set; } + public List Posts { get; set; } = []; + public int TotalCount { get; set; } + + public int CurrentPage { get; set; } + public int PageSize { get; set; } = 10; + public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize); + + public async Task OnGetAsync(int currentPage = 1) { - private readonly PostService.PostServiceClient _postClient; + Site = HttpContext.Items[PublicationSiteMiddleware.SiteContextKey] as SnPublicationSite; + CurrentPage = currentPage; + + Publisher = await rps.GetPublisher(id: Site!.PublisherId.ToString()); - public PostsModel(PostService.PostServiceClient postClient) + var request = new ListPostsRequest { - _postClient = postClient; - } + OrderBy = "date", + OrderDesc = true, + PageSize = PageSize, + PageToken = ((CurrentPage - 1) * PageSize).ToString(), + PublisherId = Site!.PublisherId.ToString() + }; - public List 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); + var response = await postClient.ListPostsAsync(request); - public async Task OnGetAsync(int currentPage = 1) + if (response?.Posts != null) { - 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; - } + Posts = response.Posts.Select(SnPost.FromProtoValue).ToList(); + TotalCount = response.TotalSize; } } -} +} \ No newline at end of file diff --git a/DysonNetwork.Zone/Pages/Posts/Details.cshtml b/DysonNetwork.Zone/Pages/Posts/Details.cshtml new file mode 100644 index 0000000..b3950ed --- /dev/null +++ b/DysonNetwork.Zone/Pages/Posts/Details.cshtml @@ -0,0 +1,70 @@ +@page "/p/{slug}" +@model DysonNetwork.Zone.Pages.Posts.DetailsModel +@{ + Layout = "_LayoutContained"; + var defaultAvatar = "https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp"; +} + +
+ @if (Model.Post != null) + { +
+
+
+
+
+ +
+
+
+
@Model.Post.Publisher?.Name
+
@Model.Post.Publisher?.Nick
+
+
+ +

@Model.Post.Title

+ + @if (Model.Post.Attachments.Any()) + { +
+ @foreach (var attachment in Model.Post.Attachments) + { + if (attachment.MimeType != null && attachment.MimeType.StartsWith("image")) + { + @attachment.Name + } + else + { + @attachment.Name + } + } +
+ } + +
+ @Model.Post.Content +
+ + @if (Model.Post.Categories.Any()) + { +
+ @foreach (var category in Model.Post.Categories) + { + @(!string.IsNullOrEmpty(category.Name) ? category.Name : category.Slug) + } +
+ } + +
+ Posted on @Model.Post.CreatedAt.ToDateTimeOffset().ToString("yyyy-MM-dd") +
+
+
+ } + else + { +
+

Post not found.

+
+ } +
diff --git a/DysonNetwork.Zone/Pages/Posts/Details.cshtml.cs b/DysonNetwork.Zone/Pages/Posts/Details.cshtml.cs new file mode 100644 index 0000000..ec9a978 --- /dev/null +++ b/DysonNetwork.Zone/Pages/Posts/Details.cshtml.cs @@ -0,0 +1,37 @@ +using DysonNetwork.Shared.Models; +using DysonNetwork.Shared.Proto; +using DysonNetwork.Zone.Publication; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace DysonNetwork.Zone.Pages.Posts; + +public class DetailsModel(PostService.PostServiceClient postClient) : PageModel +{ + [FromRoute] public string Slug { get; set; } = null!; + + public SnPublicationSite? Site { get; set; } + public SnPost? Post { get; set; } + + public async Task OnGetAsync() + { + Site = HttpContext.Items[PublicationSiteMiddleware.SiteContextKey] as SnPublicationSite; + + if (string.IsNullOrEmpty(Slug)) + return NotFound(); + + var request = new GetPostRequest { PublisherId = Site!.PublisherId.ToString() }; + if (Guid.TryParse(Slug, out var guid)) request.Id = guid.ToString(); + else request.Slug = Slug; + var response = await postClient.GetPostAsync(request); + + if (response == null) + { + return NotFound(); + } + + Post = SnPost.FromProtoValue(response); + + return Page(); + } +} \ No newline at end of file