load with public flag

This commit is contained in:
john 2025-05-18 13:40:05 +02:00
parent e3c95eb109
commit 322dd01ee0
14 changed files with 73 additions and 38 deletions

View file

@ -1,3 +1,8 @@
namespace Femto.Api.Controllers.Posts.Dto; namespace Femto.Api.Controllers.Posts.Dto;
public record CreatePostRequest(Guid AuthorId, string Content, IEnumerable<CreatePostRequestMedia> Media); public record CreatePostRequest(
Guid AuthorId,
string Content,
IEnumerable<CreatePostRequestMedia> Media,
bool? IsPublic
);

View file

@ -3,4 +3,4 @@ using JetBrains.Annotations;
namespace Femto.Api.Controllers.Posts.Dto; namespace Femto.Api.Controllers.Posts.Dto;
[PublicAPI] [PublicAPI]
public record GetAllPublicPostsResponse(IEnumerable<PublicPostDto> Posts, Guid? Next); public record GetAllPublicPostsResponse(IEnumerable<PostDto> Posts, Guid? Next);

View file

@ -3,4 +3,4 @@ using JetBrains.Annotations;
namespace Femto.Api.Controllers.Posts.Dto; namespace Femto.Api.Controllers.Posts.Dto;
[PublicAPI] [PublicAPI]
public record PublicPostAuthorDto(Guid AuthorId, string Username); public record PostAuthorDto(Guid AuthorId, string Username);

View file

@ -3,10 +3,10 @@ using JetBrains.Annotations;
namespace Femto.Api.Controllers.Posts.Dto; namespace Femto.Api.Controllers.Posts.Dto;
[PublicAPI] [PublicAPI]
public record PublicPostDto( public record PostDto(
PublicPostAuthorDto Author, PostAuthorDto Author,
Guid PostId, Guid PostId,
string Content, string Content,
IEnumerable<PublicPostMediaDto> Media, IEnumerable<PostMediaDto> Media,
DateTimeOffset CreatedAt DateTimeOffset CreatedAt
); );

View file

@ -3,4 +3,4 @@ using JetBrains.Annotations;
namespace Femto.Api.Controllers.Posts.Dto; namespace Femto.Api.Controllers.Posts.Dto;
[PublicAPI] [PublicAPI]
public record PublicPostMediaDto(Uri Url, int? Width, int? Height); public record PostMediaDto(Uri Url, int? Width, int? Height);

View file

@ -1,4 +1,5 @@
using Femto.Api.Controllers.Posts.Dto; using Femto.Api.Controllers.Posts.Dto;
using Femto.Common;
using Femto.Modules.Blog.Application; using Femto.Modules.Blog.Application;
using Femto.Modules.Blog.Application.Commands.CreatePost; using Femto.Modules.Blog.Application.Commands.CreatePost;
using Femto.Modules.Blog.Application.Queries.GetPosts; using Femto.Modules.Blog.Application.Queries.GetPosts;
@ -9,17 +10,16 @@ namespace Femto.Api.Controllers.Posts;
[ApiController] [ApiController]
[Route("posts")] [Route("posts")]
public class PostsController(IBlogModule blogModule) : ControllerBase public class PostsController(IBlogModule blogModule, ICurrentUserContext currentUserContext) : ControllerBase
{ {
[HttpGet] [HttpGet]
[Authorize] public async Task<ActionResult<GetAllPublicPostsResponse>> LoadPosts(
public async Task<ActionResult<GetAllPublicPostsResponse>> GetAllPublicPosts(
[FromQuery] GetPublicPostsSearchParams searchParams, [FromQuery] GetPublicPostsSearchParams searchParams,
CancellationToken cancellationToken CancellationToken cancellationToken
) )
{ {
var res = await blogModule.PostQuery( var res = await blogModule.PostQuery(
new GetPostsQuery new GetPostsQuery(currentUserContext.CurrentUser?.Id)
{ {
From = searchParams.From, From = searchParams.From,
Amount = searchParams.Amount ?? 20, Amount = searchParams.Amount ?? 20,
@ -30,11 +30,11 @@ public class PostsController(IBlogModule blogModule) : ControllerBase
); );
return new GetAllPublicPostsResponse( return new GetAllPublicPostsResponse(
res.Posts.Select(p => new PublicPostDto( res.Posts.Select(p => new PostDto(
new PublicPostAuthorDto(p.Author.AuthorId, p.Author.Username), new PostAuthorDto(p.Author.AuthorId, p.Author.Username),
p.PostId, p.PostId,
p.Text, 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 p.CreatedAt
)), )),
res.Next res.Next
@ -43,7 +43,7 @@ public class PostsController(IBlogModule blogModule) : ControllerBase
[HttpPost] [HttpPost]
[Authorize] [Authorize]
public async Task<ActionResult<CreatePostResponse>> Post( public async Task<ActionResult<CreatePostResponse>> CreatePost(
[FromBody] CreatePostRequest req, [FromBody] CreatePostRequest req,
CancellationToken cancellationToken CancellationToken cancellationToken
) )
@ -62,7 +62,8 @@ public class PostsController(IBlogModule blogModule) : ControllerBase
media.Width, media.Width,
media.Height media.Height
) )
) ),
req.IsPublic
), ),
cancellationToken cancellationToken
); );

View file

@ -5,7 +5,11 @@ namespace Femto.Api.Sessions;
internal static class HttpContextSessionExtensions 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( httpContext.Response.Cookies.Append(
"session", "session",
@ -14,7 +18,18 @@ internal static class HttpContextSessionExtensions
{ {
HttpOnly = true, HttpOnly = true,
Secure = cookieSettings.Secure, 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, Expires = session.Expires,
} }
); );

View file

@ -15,7 +15,8 @@ CREATE TABLE blog.post
id uuid PRIMARY KEY, id uuid PRIMARY KEY,
content text NOT NULL, content text NOT NULL,
posted_on timestamptz NOT NULL DEFAULT now(), posted_on timestamptz NOT NULL DEFAULT now(),
author_id uuid NOT NULL REFERENCES blog.author (id) on DELETE CASCADE author_id uuid NOT NULL REFERENCES blog.author (id) on DELETE CASCADE,
is_private bool NOT NULL DEFAULT false
); );
CREATE TABLE blog.post_media CREATE TABLE blog.post_media

View file

@ -2,7 +2,7 @@ using Femto.Common.Domain;
namespace Femto.Modules.Blog.Application.Commands.CreatePost; namespace Femto.Modules.Blog.Application.Commands.CreatePost;
public record CreatePostCommand(Guid AuthorId, string Content, IEnumerable<CreatePostMedia> Media) public record CreatePostCommand(Guid AuthorId, string Content, IEnumerable<CreatePostMedia> Media, bool? IsPublic)
: ICommand<Guid>; : ICommand<Guid>;
public record CreatePostMedia(Guid MediaId, Uri Url, string? Type, int Order, int? Width, int? Height); public record CreatePostMedia(Guid MediaId, Uri Url, string? Type, int Order, int? Width, int? Height);

View file

@ -23,6 +23,8 @@ internal class CreatePostCommandHandler(BlogContext context)
.ToList() .ToList()
); );
post.IsPublic = request.IsPublic is true;
await context.AddAsync(post, cancellationToken); await context.AddAsync(post, cancellationToken);
return post.Id; return post.Id;

View file

@ -3,23 +3,22 @@ using Femto.Modules.Blog.Application.Queries.GetPosts.Dto;
namespace Femto.Modules.Blog.Application.Queries.GetPosts; namespace Femto.Modules.Blog.Application.Queries.GetPosts;
public class GetPostsQuery : IQuery<GetPostsQueryResult> public record GetPostsQuery(Guid? CurrentUserId) : IQuery<GetPostsQueryResult>
{ {
public Guid? From { get; init; } public Guid? From { get; init; }
public int Amount { get; init; } = 20; public int Amount { get; init; } = 20;
public Guid? AuthorId { get; init; } public Guid? AuthorId { get; init; }
public string? Author { get; set; } public string? Author { get; init; }
/// <summary> /// <summary>
/// Default is to load in reverse chronological order /// Default is to load in reverse chronological order
/// TODO this is not exposed on the client as it probably wouldn't work that well /// TODO this is not exposed on the client as it probably wouldn't work that well
/// </summary> /// </summary>
public GetPostsDirection Direction { get; init; } = GetPostsDirection.Backward; public GetPostsDirection Direction { get; init; } = GetPostsDirection.Backward;
} }
public enum GetPostsDirection public enum GetPostsDirection
{ {
Forward, Forward,
Backward Backward,
} }

View file

@ -17,6 +17,10 @@ public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory)
var orderBy = query.Direction is GetPostsDirection.Backward ? "desc" : "asc"; var orderBy = query.Direction is GetPostsDirection.Backward ? "desc" : "asc";
var pageFilter = query.Direction is GetPostsDirection.Backward ? "<=" : ">="; 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 // lang=sql
var sql = $$""" var sql = $$"""
@ -25,6 +29,7 @@ public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory)
from blog.post from blog.post
inner join blog.author on blog.author.id = blog.post.author_id inner join blog.author on blog.author.id = blog.post.author_id
where (@username is null or blog.author.username = @username) 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 (@authorGuid is null or blog.author.id = @authorGuid)
and (@cursor is null or blog.post.id {{pageFilter}} @cursor) and (@cursor is null or blog.post.id {{pageFilter}} @cursor)
order by blog.post.id {{orderBy}} order by blog.post.id {{orderBy}}
@ -48,11 +53,12 @@ public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory)
sql, sql,
new new
{ {
username = query.Author, username,
authorGuid = query.AuthorId, authorGuid,
cursor = query.From, cursor,
// load an extra one to take for the curst // load an extra one to take for the cursor
amount = query.Amount + 1, amount = query.Amount + 1,
showPrivate,
} }
); );

View file

@ -1,6 +1,5 @@
using Femto.Common.Domain; using Femto.Common.Domain;
using Femto.Modules.Blog.Domain.Posts.Events; using Femto.Modules.Blog.Domain.Posts.Events;
using Femto.Modules.Blog.Domain.Posts.Rules;
namespace Femto.Modules.Blog.Domain.Posts; namespace Femto.Modules.Blog.Domain.Posts;
@ -10,6 +9,7 @@ internal class Post : Entity
public Guid AuthorId { get; private set; } public Guid AuthorId { get; private set; }
public string Content { get; private set; } = null!; public string Content { get; private set; } = null!;
public IList<PostMedia> Media { get; private set; } public IList<PostMedia> Media { get; private set; }
public bool IsPublic { get; set; }
private Post() { } private Post() { }

View file

@ -16,6 +16,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Femto.Modules.Auth", "Femto
EndProject 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}" 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 EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{0DE32333-FDA4-450F-BA3D-58389F3AACE1}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -56,5 +58,9 @@ Global
{1AC1DA1D-54B0-44FC-9FDF-9C2E68BB8ABB}.Release|Any CPU.Build.0 = Release|Any CPU {1AC1DA1D-54B0-44FC-9FDF-9C2E68BB8ABB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution 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 EndGlobalSection
EndGlobal EndGlobal