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)
{

View file

@ -12,6 +12,7 @@ namespace Yavsc.Blogs.Controllers
using Yavsc.Server.Helpers;
using Yavsc.Abstract.Helpers;
using Yavsc.Server.Models.FileSystem;
using Microsoft.Extensions.Options;
[Authorize,Route(APIPrefix + "/fs")]
public partial class FileSystemApiController : Controller
@ -19,15 +20,18 @@ namespace Yavsc.Blogs.Controllers
readonly ApplicationDbContext dbContext;
private readonly IAuthorizationService AuthorizationService;
private readonly ILogger _logger;
private readonly SiteSettings siteSettings;
public FileSystemApiController(ApplicationDbContext context,
IAuthorizationService authorizationService,
ILoggerFactory loggerFactory)
ILoggerFactory loggerFactory,
IOptions<SiteSettings> siteSettings)
{
AuthorizationService = authorizationService;
dbContext = context;
_logger = loggerFactory.CreateLogger<FileSystemApiController>();
this.siteSettings = siteSettings.Value;
}
[HttpGet()]
@ -41,7 +45,7 @@ namespace Yavsc.Blogs.Controllers
{
if (!ModelState.IsValid) return new BadRequestObjectResult(ModelState);
// _logger.LogInformation($"listing files from {User.Identity.Name}{subdir}");
var files = AbstractFileSystemHelpers.GetUserFiles(User.GetUserId(), subdir);
var files = FileSystemHelpers.GetUserFiles(siteSettings, User.GetUserId(), subdir);
return Ok(files);
}
@ -53,7 +57,7 @@ namespace Yavsc.Blogs.Controllers
List<FileReceivedInfo> received = new List<FileReceivedInfo>();
InvalidPathException pathex = null;
try {
destDir = User.EnsureDestinationDirectory(subdir);
destDir = User.EnsureDestinationDirectory(subdir, siteSettings);
} catch (InvalidPathException ex) {
pathex = ex;
}
@ -108,7 +112,7 @@ namespace Yavsc.Blogs.Controllers
var user = dbContext.Users.Single(
u => u.Id == uid
);
var info = user.MoveUserFileToDir(query.Id, query.To);
var info = user.MoveUserFileToDir(query.Id, query.To, siteSettings);
if (!info.Done) return new BadRequestObjectResult(info);
return Ok(new { moved = query.Id });
}
@ -131,11 +135,11 @@ namespace Yavsc.Blogs.Controllers
try {
if (Config.UserFilesOptions.FileProvider.GetFileInfo(Path.Combine(user.UserName, query.Id)).Exists)
{
var result = user.MoveUserFile(query.Id, query.To);
var result = user.MoveUserFile(query.Id, query.To, siteSettings);
if (!result.Done) return new BadRequestObjectResult(result);
}
else {
var result = user.MoveUserDir(query.Id, query.To);
var result = user.MoveUserDir(query.Id, query.To, siteSettings);
if (!result.Done) return new BadRequestObjectResult(result);
}
}
@ -161,7 +165,7 @@ namespace Yavsc.Blogs.Controllers
);
try {
var result = user.DeleteUserDirOrFile(id);
var result = user.DeleteUserDirOrFile(id, siteSettings);
if (!result.Done)
return new BadRequestObjectResult(result);
}

View file

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.SignalR;
using Yavsc.Server.Helpers;
using static Yavsc.Constants;
using Yavsc.Server.Hubs;
using Microsoft.Extensions.Options;
namespace Yavsc.Blogs.Controllers
{
@ -21,13 +22,20 @@ namespace Yavsc.Blogs.Controllers
private readonly IHubContext<ChatHub> hubContext;
readonly ApplicationDbContext dbContext;
public FileSystemStreamController(ApplicationDbContext context, ILiveProcessor liveProcessor, ILoggerFactory loggerFactory,
IHubContext<ChatHub> hubContext)
private readonly SiteSettings siteSettings;
public FileSystemStreamController(
ApplicationDbContext context,
ILiveProcessor liveProcessor,
ILoggerFactory loggerFactory,
IHubContext<ChatHub> hubContext,
IOptions<SiteSettings> siteSettings)
{
this.dbContext = context;
this.logger = loggerFactory.CreateLogger<FileSystemStreamController>();
this.liveProcessor = liveProcessor;
this.hubContext = hubContext;
this.siteSettings = siteSettings.Value;
}
[Authorize, Route("put/{filename}")]
@ -55,7 +63,7 @@ namespace Yavsc.Blogs.Controllers
);
string destDir = HttpContext.User.EnsureDestinationDirectory(filePath);
string destDir = HttpContext.User.EnsureDestinationDirectory(filePath, siteSettings);
logger.LogInformation($"Saving flow to {destDir}");
var userId = User.GetUserId();
var user = await dbContext.Users.FirstAsync(u => u.Id == userId);

View file

@ -8,7 +8,8 @@
"CorsAllowedOrigins": [
"https://localhost:5005",
"https://api.pschneider.fr"
]
],
"Blog": "blog"
},
"ConnectionStrings": {
"YavscConnection": "Server=localhost;Port=5432;Database=lame-db-name;Username=lame-user-name;Password=lame-password;"