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;
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;
[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;
[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;
[PublicAPI]
public record PublicPostDto(
PublicPostAuthorDto Author,
public record PostDto(
PostAuthorDto Author,
Guid PostId,
string Content,
IEnumerable<PublicPostMediaDto> Media,
IEnumerable<PostMediaDto> Media,
DateTimeOffset CreatedAt
);

View file

@ -3,4 +3,4 @@ using JetBrains.Annotations;
namespace Femto.Api.Controllers.Posts.Dto;
[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.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<ActionResult<GetAllPublicPostsResponse>> GetAllPublicPosts(
public async Task<ActionResult<GetAllPublicPostsResponse>> 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<ActionResult<CreatePostResponse>> Post(
public async Task<ActionResult<CreatePostResponse>> CreatePost(
[FromBody] CreatePostRequest req,
CancellationToken cancellationToken
)
@ -62,7 +62,8 @@ public class PostsController(IBlogModule blogModule) : ControllerBase
media.Width,
media.Height
)
)
),
req.IsPublic
),
cancellationToken
);

View file

@ -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,7 +18,18 @@ 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,
}
);

View file

@ -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

View file

@ -2,7 +2,7 @@ using Femto.Common.Domain;
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>;
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()
);
post.IsPublic = request.IsPublic is true;
await context.AddAsync(post, cancellationToken);
return post.Id;

View file

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

View file

@ -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,
}
);

View file

@ -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,10 +9,11 @@ internal class Post : Entity
public Guid AuthorId { get; private set; }
public string Content { get; private set; } = null!;
public IList<PostMedia> Media { get; private set; }
public bool IsPublic { get; set; }
private Post() { }
public Post(Guid authorId,string content, IList<PostMedia> media)
public Post(Guid authorId, string content, IList<PostMedia> media)
{
this.Id = Guid.CreateVersion7();
this.AuthorId = authorId;

View file

@ -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