Merge pull request 'fix/postit-acl' (#39) from fix/postit-acl into release/1.0.8-rc1

Reviewed-on: #39
This commit is contained in:
Paul Schneider 2026-08-20 01:24:42 +01:00
commit e75993ea36
4 changed files with 56 additions and 12 deletions

View file

@ -67,7 +67,7 @@
<Border Grid.Row="1" BorderBrush="Gray" BorderThickness="1" Padding="8">
<ListBox ItemsSource="{Binding FilteredPosts}" SelectedItem="{Binding SelectedPost, Mode=TwoWay}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="40">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="models:BlogPostDto">
<StackPanel Spacing="4">

View file

@ -88,7 +88,7 @@ public sealed class CircleMembersApiTests : IClassFixture<BlogsWebServerFixture>
}
private string MembersUrl(long circleId)
=> $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/api/circle/{circleId}/members";
=> $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{Constants.APIPrefix}/circle/{circleId}/members";
private HttpClient NewClient(string subject)
{

View file

@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Yavsc.Blogspot;
using Yavsc.Models.Blog;
using Yavsc.Server.Exceptions;
using Yavsc.Server.Helpers;
using static Yavsc.Blogs.Constants;

View file

@ -4,11 +4,12 @@ using Microsoft.EntityFrameworkCore;
using Yavsc.Models;
using Yavsc.Models.Relationship;
using Yavsc.Server.Helpers;
using static Yavsc.Blogs.Constants;
namespace Yavsc.Blogs.Controllers
{
[Produces("application/json")]
[Route("api/circle")]
[Route(APIPrefix +"/circle")]
public class CircleApiController : Controller
{
private readonly ApplicationDbContext _context;
@ -56,12 +57,25 @@ namespace Yavsc.Blogs.Controllers
/// <summary>
/// Replaces a circle. The caller must own it; the server
/// reasserts ownership regardless of any OwnerId the client
/// tries to put in the body.
/// reasserts ownership regardless of any <c>OwnerId</c>
/// the client tries to put in the body.
///
/// <para>The body shape is a <see cref="CircleDto"/> — a
/// flat, navigation-free projection — not the EF entity.
/// The EF entity carries <c>[JsonIgnore]</c>-decorated
/// navigation properties (<c>Owner</c>, <c>Members</c>)
/// that bind to server-only types (<c>ApplicationUser</c>,
/// <c>CircleMember</c>); keeping the wire shape as a
/// DTO avoids any future regression where the entity
/// grows a navigable property that System.Text.Json
/// refuses to materialise. The client-side mirror lives
/// in <c>Yavsc.Api.Client.Dtos.CircleDto</c>.</para>
/// </summary>
// PUT: api/circle/5
[HttpPut("{id}")]
public async Task<IActionResult> PutCircle([FromRoute] long id, [FromBody] Circle circle)
public async Task<IActionResult> PutCircle(
[FromRoute] long id,
[FromBody] CircleDto circle)
{
if (!ModelState.IsValid)
{
@ -81,9 +95,14 @@ namespace Yavsc.Blogs.Controllers
return new ChallengeResult();
}
// Force OwnerId to the caller; the body value is ignored.
circle.OwnerId = uid;
_context.Entry(circle).State = EntityState.Modified;
// Map the wire shape onto the entity. OwnerId is
// forced to the caller regardless of what the body
// says; Name and Public come from the body.
existing.Name = circle.Name;
existing.Public = circle.Public;
existing.OwnerId = uid;
_context.Entry(existing).State = EntityState.Modified;
try
{
@ -110,7 +129,7 @@ namespace Yavsc.Blogs.Controllers
/// </summary>
// POST: api/circle
[HttpPost]
public async Task<IActionResult> PostCircle([FromBody] Circle circle)
public async Task<IActionResult> PostCircle([FromBody] CircleDto circle)
{
if (!ModelState.IsValid)
{
@ -119,8 +138,14 @@ namespace Yavsc.Blogs.Controllers
var uid = User.GetUserId();
circle.OwnerId = uid;
Circle newCircle = new Circle
{
OwnerId = User.GetUserId(),
Name = circle.Name,
Public = circle.Public
};
_context.Circle.Add(circle);
_context.Circle.Add(newCircle);
try
{
await _context.SaveChangesAsync(User.GetUserId());
@ -321,6 +346,26 @@ namespace Yavsc.Blogs.Controllers
}
}
/// <summary>
/// Wire shape for <c>PUT /api/circle/{id}</c>. Flat by
/// design — navigation properties (<c>Owner</c>,
/// <c>Members</c>) live on the EF entity only and never
/// cross the wire.
///
/// <para>Field names match the JSON the server emits
/// (camelCase via ASP.NET Core's Web defaults), so no
/// <c>[JsonPropertyName]</c> attributes are required.
/// Mirrors the client-side <c>Yavsc.Api.Client.Dtos.CircleDto</c>
/// — keep them in sync.</para>
/// </summary>
public sealed class CircleDto
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public string OwnerId { get; set; } = string.Empty;
public bool Public { get; set; }
}
/// <summary>
/// Wire shape for <c>GET /api/circle/{id}/members</c>.
/// Mirrors <see cref="UserSearchResultDto"/> but stops