From 322dd01ee06914f272f28f0224f699a5bbbe8c72 Mon Sep 17 00:00:00 2001 From: john Date: Sun, 18 May 2025 13:40:05 +0200 Subject: [PATCH] load with public flag --- .../Posts/Dto/CreatePostRequest.cs | 7 ++++++- .../Posts/Dto/GetAllPublicPostsResponse.cs | 2 +- ...ublicPostAuthorDto.cs => PostAuthorDto.cs} | 2 +- .../Dto/{PublicPostDto.cs => PostDto.cs} | 6 +++--- ...{PublicPostMediaDto.cs => PostMediaDto.cs} | 2 +- .../Controllers/Posts/PostsController.cs | 19 +++++++++-------- .../Sessions/HttpContextSessionExtensions.cs | 21 ++++++++++++++++--- .../Migrations/20250517195735_InitBlog.sql | 9 ++++---- .../Commands/CreatePost/CreatePostCommand.cs | 2 +- .../CreatePost/CreatePostCommandHandler.cs | 2 ++ .../Queries/GetPosts/GetPostsQuery.cs | 11 +++++----- .../Queries/GetPosts/GetPostsQueryHandler.cs | 14 +++++++++---- Femto.Modules.Blog/Domain/Posts/Post.cs | 8 +++---- FemtoBackend.sln | 6 ++++++ 14 files changed, 73 insertions(+), 38 deletions(-) rename Femto.Api/Controllers/Posts/Dto/{PublicPostAuthorDto.cs => PostAuthorDto.cs} (56%) rename Femto.Api/Controllers/Posts/Dto/{PublicPostDto.cs => PostDto.cs} (59%) rename Femto.Api/Controllers/Posts/Dto/{PublicPostMediaDto.cs => PostMediaDto.cs} (56%) diff --git a/Femto.Api/Controllers/Posts/Dto/CreatePostRequest.cs b/Femto.Api/Controllers/Posts/Dto/CreatePostRequest.cs index 3413de3..8a36d62 100644 --- a/Femto.Api/Controllers/Posts/Dto/CreatePostRequest.cs +++ b/Femto.Api/Controllers/Posts/Dto/CreatePostRequest.cs @@ -1,3 +1,8 @@ namespace Femto.Api.Controllers.Posts.Dto; -public record CreatePostRequest(Guid AuthorId, string Content, IEnumerable Media); \ No newline at end of file +public record CreatePostRequest( + Guid AuthorId, + string Content, + IEnumerable Media, + bool? IsPublic +); diff --git a/Femto.Api/Controllers/Posts/Dto/GetAllPublicPostsResponse.cs b/Femto.Api/Controllers/Posts/Dto/GetAllPublicPostsResponse.cs index 535f4af..ededad1 100644 --- a/Femto.Api/Controllers/Posts/Dto/GetAllPublicPostsResponse.cs +++ b/Femto.Api/Controllers/Posts/Dto/GetAllPublicPostsResponse.cs @@ -3,4 +3,4 @@ using JetBrains.Annotations; namespace Femto.Api.Controllers.Posts.Dto; [PublicAPI] -public record GetAllPublicPostsResponse(IEnumerable Posts, Guid? Next); \ No newline at end of file +public record GetAllPublicPostsResponse(IEnumerable Posts, Guid? Next); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/PublicPostAuthorDto.cs b/Femto.Api/Controllers/Posts/Dto/PostAuthorDto.cs similarity index 56% rename from Femto.Api/Controllers/Posts/Dto/PublicPostAuthorDto.cs rename to Femto.Api/Controllers/Posts/Dto/PostAuthorDto.cs index d9931da..8b04b35 100644 --- a/Femto.Api/Controllers/Posts/Dto/PublicPostAuthorDto.cs +++ b/Femto.Api/Controllers/Posts/Dto/PostAuthorDto.cs @@ -3,4 +3,4 @@ using JetBrains.Annotations; namespace Femto.Api.Controllers.Posts.Dto; [PublicAPI] -public record PublicPostAuthorDto(Guid AuthorId, string Username); \ No newline at end of file +public record PostAuthorDto(Guid AuthorId, string Username); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/PublicPostDto.cs b/Femto.Api/Controllers/Posts/Dto/PostDto.cs similarity index 59% rename from Femto.Api/Controllers/Posts/Dto/PublicPostDto.cs rename to Femto.Api/Controllers/Posts/Dto/PostDto.cs index d357dd3..c48014a 100644 --- a/Femto.Api/Controllers/Posts/Dto/PublicPostDto.cs +++ b/Femto.Api/Controllers/Posts/Dto/PostDto.cs @@ -3,10 +3,10 @@ using JetBrains.Annotations; namespace Femto.Api.Controllers.Posts.Dto; [PublicAPI] -public record PublicPostDto( - PublicPostAuthorDto Author, +public record PostDto( + PostAuthorDto Author, Guid PostId, string Content, - IEnumerable Media, + IEnumerable Media, DateTimeOffset CreatedAt ); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/Dto/PublicPostMediaDto.cs b/Femto.Api/Controllers/Posts/Dto/PostMediaDto.cs similarity index 56% rename from Femto.Api/Controllers/Posts/Dto/PublicPostMediaDto.cs rename to Femto.Api/Controllers/Posts/Dto/PostMediaDto.cs index e3e0c59..f82892f 100644 --- a/Femto.Api/Controllers/Posts/Dto/PublicPostMediaDto.cs +++ b/Femto.Api/Controllers/Posts/Dto/PostMediaDto.cs @@ -3,4 +3,4 @@ using JetBrains.Annotations; namespace Femto.Api.Controllers.Posts.Dto; [PublicAPI] -public record PublicPostMediaDto(Uri Url, int? Width, int? Height); \ No newline at end of file +public record PostMediaDto(Uri Url, int? Width, int? Height); \ No newline at end of file diff --git a/Femto.Api/Controllers/Posts/PostsController.cs b/Femto.Api/Controllers/Posts/PostsController.cs index 0d8f4e8..9eeeafc 100644 --- a/Femto.Api/Controllers/Posts/PostsController.cs +++ b/Femto.Api/Controllers/Posts/PostsController.cs @@ -1,4 +1,5 @@ using Femto.Api.Controllers.Posts.Dto; +using Femto.Common; using Femto.Modules.Blog.Application; using Femto.Modules.Blog.Application.Commands.CreatePost; using Femto.Modules.Blog.Application.Queries.GetPosts; @@ -9,17 +10,16 @@ namespace Femto.Api.Controllers.Posts; [ApiController] [Route("posts")] -public class PostsController(IBlogModule blogModule) : ControllerBase +public class PostsController(IBlogModule blogModule, ICurrentUserContext currentUserContext) : ControllerBase { [HttpGet] - [Authorize] - public async Task> GetAllPublicPosts( + public async Task> LoadPosts( [FromQuery] GetPublicPostsSearchParams searchParams, CancellationToken cancellationToken ) { var res = await blogModule.PostQuery( - new GetPostsQuery + new GetPostsQuery(currentUserContext.CurrentUser?.Id) { From = searchParams.From, Amount = searchParams.Amount ?? 20, @@ -30,11 +30,11 @@ public class PostsController(IBlogModule blogModule) : ControllerBase ); return new GetAllPublicPostsResponse( - res.Posts.Select(p => new PublicPostDto( - new PublicPostAuthorDto(p.Author.AuthorId, p.Author.Username), + res.Posts.Select(p => new PostDto( + new PostAuthorDto(p.Author.AuthorId, p.Author.Username), p.PostId, p.Text, - p.Media.Select(m => new PublicPostMediaDto(m.Url, m.Width, m.Height)), + p.Media.Select(m => new PostMediaDto(m.Url, m.Width, m.Height)), p.CreatedAt )), res.Next @@ -43,7 +43,7 @@ public class PostsController(IBlogModule blogModule) : ControllerBase [HttpPost] [Authorize] - public async Task> Post( + public async Task> CreatePost( [FromBody] CreatePostRequest req, CancellationToken cancellationToken ) @@ -62,7 +62,8 @@ public class PostsController(IBlogModule blogModule) : ControllerBase media.Width, media.Height ) - ) + ), + req.IsPublic ), cancellationToken ); diff --git a/Femto.Api/Sessions/HttpContextSessionExtensions.cs b/Femto.Api/Sessions/HttpContextSessionExtensions.cs index 02c06cb..2b176ed 100644 --- a/Femto.Api/Sessions/HttpContextSessionExtensions.cs +++ b/Femto.Api/Sessions/HttpContextSessionExtensions.cs @@ -5,7 +5,11 @@ namespace Femto.Api.Sessions; internal static class HttpContextSessionExtensions { - public static void SetSession(this HttpContext httpContext, Session session, CookieSettings cookieSettings) + public static void SetSession( + this HttpContext httpContext, + Session session, + CookieSettings cookieSettings + ) { httpContext.Response.Cookies.Append( "session", @@ -14,9 +18,20 @@ internal static class HttpContextSessionExtensions { HttpOnly = true, Secure = cookieSettings.Secure, - SameSite = cookieSettings.SameSite? SameSiteMode.Strict : SameSiteMode.None, + SameSite = cookieSettings.SameSite ? SameSiteMode.Strict : SameSiteMode.Unspecified, + Expires = session.Expires, + } + ); + + httpContext.Response.Cookies.Append( + "hasSession", + "true", + new CookieOptions + { + Secure = cookieSettings.Secure, + SameSite = cookieSettings.SameSite ? SameSiteMode.Strict : SameSiteMode.Unspecified, Expires = session.Expires, } ); } -} \ No newline at end of file +} diff --git a/Femto.Database/Migrations/20250517195735_InitBlog.sql b/Femto.Database/Migrations/20250517195735_InitBlog.sql index 6aeb9da..731aac8 100644 --- a/Femto.Database/Migrations/20250517195735_InitBlog.sql +++ b/Femto.Database/Migrations/20250517195735_InitBlog.sql @@ -12,10 +12,11 @@ CREATE TABLE blog.author CREATE TABLE blog.post ( - id uuid PRIMARY KEY, - content text NOT NULL, - posted_on timestamptz NOT NULL DEFAULT now(), - author_id uuid NOT NULL REFERENCES blog.author (id) on DELETE CASCADE + id uuid PRIMARY KEY, + content text NOT NULL, + posted_on timestamptz NOT NULL DEFAULT now(), + author_id uuid NOT NULL REFERENCES blog.author (id) on DELETE CASCADE, + is_private bool NOT NULL DEFAULT false ); CREATE TABLE blog.post_media diff --git a/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommand.cs b/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommand.cs index 1ada9a7..d10c496 100644 --- a/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommand.cs +++ b/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommand.cs @@ -2,7 +2,7 @@ using Femto.Common.Domain; namespace Femto.Modules.Blog.Application.Commands.CreatePost; -public record CreatePostCommand(Guid AuthorId, string Content, IEnumerable Media) +public record CreatePostCommand(Guid AuthorId, string Content, IEnumerable Media, bool? IsPublic) : ICommand; public record CreatePostMedia(Guid MediaId, Uri Url, string? Type, int Order, int? Width, int? Height); diff --git a/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommandHandler.cs b/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommandHandler.cs index 6867dbc..cda4b2d 100644 --- a/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommandHandler.cs +++ b/Femto.Modules.Blog/Application/Commands/CreatePost/CreatePostCommandHandler.cs @@ -22,6 +22,8 @@ internal class CreatePostCommandHandler(BlogContext context) )) .ToList() ); + + post.IsPublic = request.IsPublic is true; await context.AddAsync(post, cancellationToken); diff --git a/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQuery.cs b/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQuery.cs index f202c85..f8af9d2 100644 --- a/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQuery.cs +++ b/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQuery.cs @@ -3,23 +3,22 @@ using Femto.Modules.Blog.Application.Queries.GetPosts.Dto; namespace Femto.Modules.Blog.Application.Queries.GetPosts; -public class GetPostsQuery : IQuery +public record GetPostsQuery(Guid? CurrentUserId) : IQuery { public Guid? From { get; init; } public int Amount { get; init; } = 20; public Guid? AuthorId { get; init; } - public string? Author { get; set; } - + public string? Author { get; init; } + /// /// Default is to load in reverse chronological order /// TODO this is not exposed on the client as it probably wouldn't work that well /// public GetPostsDirection Direction { get; init; } = GetPostsDirection.Backward; - } public enum GetPostsDirection { Forward, - Backward -} \ No newline at end of file + Backward, +} diff --git a/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs b/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs index a731f66..7ace8a6 100644 --- a/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs +++ b/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs @@ -17,6 +17,10 @@ public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory) var orderBy = query.Direction is GetPostsDirection.Backward ? "desc" : "asc"; var pageFilter = query.Direction is GetPostsDirection.Backward ? "<=" : ">="; + var username = query.Author; + var authorGuid = query.AuthorId; + var cursor = query.From; + var showPrivate = query.CurrentUserId is not null; // lang=sql var sql = $$""" @@ -25,6 +29,7 @@ public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory) from blog.post inner join blog.author on blog.author.id = blog.post.author_id where (@username is null or blog.author.username = @username) + and (@showPrivate or blog.post.is_public = true) and (@authorGuid is null or blog.author.id = @authorGuid) and (@cursor is null or blog.post.id {{pageFilter}} @cursor) order by blog.post.id {{orderBy}} @@ -48,11 +53,12 @@ public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory) sql, new { - username = query.Author, - authorGuid = query.AuthorId, - cursor = query.From, - // load an extra one to take for the curst + username, + authorGuid, + cursor, + // load an extra one to take for the cursor amount = query.Amount + 1, + showPrivate, } ); diff --git a/Femto.Modules.Blog/Domain/Posts/Post.cs b/Femto.Modules.Blog/Domain/Posts/Post.cs index 49a6aaf..65f90d6 100644 --- a/Femto.Modules.Blog/Domain/Posts/Post.cs +++ b/Femto.Modules.Blog/Domain/Posts/Post.cs @@ -1,6 +1,5 @@ using Femto.Common.Domain; using Femto.Modules.Blog.Domain.Posts.Events; -using Femto.Modules.Blog.Domain.Posts.Rules; namespace Femto.Modules.Blog.Domain.Posts; @@ -10,16 +9,17 @@ internal class Post : Entity public Guid AuthorId { get; private set; } public string Content { get; private set; } = null!; public IList Media { get; private set; } + public bool IsPublic { get; set; } private Post() { } - public Post(Guid authorId,string content, IList media) + public Post(Guid authorId, string content, IList media) { this.Id = Guid.CreateVersion7(); this.AuthorId = authorId; this.Content = content; this.Media = media; - + this.AddDomainEvent(new PostCreated(this)); } -} \ No newline at end of file +} diff --git a/FemtoBackend.sln b/FemtoBackend.sln index 8916568..f8b8eaf 100644 --- a/FemtoBackend.sln +++ b/FemtoBackend.sln @@ -16,6 +16,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Femto.Modules.Auth", "Femto EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Femto.Modules.Auth.Contracts", "Femto.Modules.Auth.Contracts\Femto.Modules.Auth.Contracts.csproj", "{1AC1DA1D-54B0-44FC-9FDF-9C2E68BB8ABB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{0DE32333-FDA4-450F-BA3D-58389F3AACE1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -56,5 +58,9 @@ Global {1AC1DA1D-54B0-44FC-9FDF-9C2E68BB8ABB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution + {7E138EF6-E075-4896-93C0-923024F0CA78} = {0DE32333-FDA4-450F-BA3D-58389F3AACE1} + {1AC1DA1D-54B0-44FC-9FDF-9C2E68BB8ABB} = {0DE32333-FDA4-450F-BA3D-58389F3AACE1} + {095295C8-4C8C-4691-ABFA-56CD2FE3CD21} = {0DE32333-FDA4-450F-BA3D-58389F3AACE1} + {AC9FBF11-FF29-4A80-B9EA-AFDF1E3DCA80} = {0DE32333-FDA4-450F-BA3D-58389F3AACE1} EndGlobalSection EndGlobal