refacto site settings
Some checks failed
Dotnet build and test / build (pull_request) Failing after 8m29s

This commit is contained in:
Paul Schneider 2026-09-17 22:22:18 +01:00
commit 3bc4142383
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
33 changed files with 399 additions and 315 deletions

View file

@ -57,12 +57,31 @@ namespace Yavsc.Blogs.Controllers
[HttpPut("{id}")]
public async Task<IActionResult> PutBlog(long id)
{
var blog = await ReadPutBlogRequestAsync();
var blog = await ReadBlogRequestAsync();
if (blog is null)
{
return BadRequest(ModelState);
}
// Without [FromBody], the data-annotations attributes
// ([Required] on Title, etc.) are no longer evaluated by
// the model binder — the body is read manually. Run
// validation explicitly so an empty Title still yields
// 400 (and not a 500 from the DB NOT NULL constraint).
var validationContext = new System.ComponentModel.DataAnnotations.ValidationContext(blog);
var validationResults = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
if (!System.ComponentModel.DataAnnotations.Validator.TryValidateObject(
blog, validationContext, validationResults, validateAllProperties: true))
{
foreach (var result in validationResults)
{
ModelState.AddModelError(
result.MemberNames.FirstOrDefault() ?? string.Empty,
result.ErrorMessage ?? "Invalid value.");
}
return BadRequest(ModelState);
}
// These properties are server-managed or optional graph members and
// should not block JSON payloads coming from API clients.
ModelState.Remove(nameof(Models.Blog.BlogPost.Author));
@ -93,7 +112,8 @@ namespace Yavsc.Blogs.Controllers
try
{
await blogSpotService.Modify(User, blog, files);
await blogSpotService.Modify(User, blog);
blogSpotService.AttachFiles(files, User.GetUserId(), id);
}
catch (AuthorizationFailureException)
{
@ -105,8 +125,14 @@ namespace Yavsc.Blogs.Controllers
// POST: api/v1/blogspot
[HttpPost]
public IActionResult PostBlog([FromBody] Models.Blog.BlogPost blog)
public async Task<IActionResult> PostBlog()
{
var blog = await ReadBlogRequestAsync();
if (blog is null)
{
return BadRequest(ModelState);
}
// These properties are server-managed or optional graph members and
// should not block JSON payloads coming from API clients.
ModelState.Remove(nameof(Models.Blog.BlogPost.Author));
@ -115,35 +141,46 @@ namespace Yavsc.Blogs.Controllers
ModelState.Remove(nameof(Models.Blog.BlogPost.UserCreated));
ModelState.Remove(nameof(Models.Blog.BlogPost.UserModified));
// Without [FromBody], the data-annotations attributes
// ([Required] on Title, etc.) are no longer evaluated by
// the model binder — the body is read manually. Run
// validation explicitly so an empty Title still yields
// 400 (and not a 500 from the DB NOT NULL constraint).
var validationContext = new System.ComponentModel.DataAnnotations.ValidationContext(blog);
var validationResults = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
if (!System.ComponentModel.DataAnnotations.Validator.TryValidateObject(
blog, validationContext, validationResults, validateAllProperties: true))
{
foreach (var result in validationResults)
{
ModelState.AddModelError(
result.MemberNames.FirstOrDefault() ?? string.Empty,
result.ErrorMessage ?? "Invalid value.");
}
return BadRequest(ModelState);
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// The BlogSpotService.Create() signature requires an
// IFormFileCollection for file uploads. Reading
// Request.Form.Files when the request is a plain JSON
// body (e.g. from PostIt) throws
// "This request does not have a Content-Type header.
// Forms are available from requests with bodies like
// POSTs and a form Content-Type of either
// application/x-www-form-urlencoded or
// multipart/form-data."
//
// Two valid use cases for this endpoint:
// 1. JSON body only (no files) — PostIt path.
// 2. multipart/form-data with a 'blog' field + 0..N
// files — future browser / server-rendered path.
// files — PostIt "create with attachments" path.
//
// Branch on HasFormContentType: pass the form files when
// present, pass an empty collection otherwise. The
// FileSystem branch in BlogSpotService.Create then
// short-circuits to "no files to handle".
// present, pass an empty collection otherwise. Reading
// Request.Form.Files on a plain JSON request throws
// "This request does not have a Content-Type header…",
// and AttachFiles short-circuits on an empty collection.
var files = Request.HasFormContentType
? Request.Form.Files
: (IFormFileCollection)new FormFileCollection();
var uid = User.GetUserId();
var post = blogSpotService.Create(uid, blog, files);
var post = blogSpotService.Create(uid, blog);
blogSpotService.AttachFiles(files, uid, post.Id);
return CreatedAtRoute("GetBlog", new { id = post.Id },
post.GetPayload());
}
@ -210,7 +247,17 @@ namespace Yavsc.Blogs.Controllers
base.Dispose(disposing);
}
private async Task<Models.Blog.BlogPost?> ReadPutBlogRequestAsync()
/// <summary>
/// Read a <see cref="Models.Blog.BlogPost"/> from either a
/// JSON body or a multipart/form-data request carrying the
/// payload in a <c>blog</c> form field. Shared by
/// <see cref="PostBlog"/> and <see cref="PutBlog"/>: both
/// accept the two shapes so PostIt can attach files on
/// create as well as on update. Returns <c>null</c> (with
/// the reason recorded in <c>ModelState</c>) when the
/// multipart form has no usable <c>blog</c> field.
/// </summary>
private async Task<Models.Blog.BlogPost?> ReadBlogRequestAsync()
{
if (!Request.HasFormContentType)
{