From b25e0e842e8dd9fac4d79d759e2eaebcb3a246bd Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Mon, 3 Aug 2026 01:32:59 +0100
Subject: [PATCH 01/24] refacto blogPost
---
src/PostIt.Tests/BlogApiTestFakes.cs | 1 +
src/PostIt/PostIt/App.axaml.cs | 30 +++++++-------
src/PostIt/PostIt/Models/BlogPost.cs | 41 ++++++++++++++-----
.../PostIt/ViewModels/HomePageViewModel.cs | 11 ++++-
src/PostIt/PostIt/Views/MainPage.axaml.cs | 2 +-
src/Yavsc.Abstract/Blogspot/IBlog.cs | 19 ---------
src/Yavsc.Abstract/Blogspot/IBlogPost.cs | 15 +++++++
.../Blogspot/IBlogPostPayLoad.cs | 9 ++++
.../Controllers/BlogApiController.cs | 1 +
src/Yavsc.Server/Models/Blog/BlogPost.cs | 7 ++--
src/Yavsc.Server/Services/BlogSpotService.cs | 1 +
11 files changed, 85 insertions(+), 52 deletions(-)
delete mode 100644 src/Yavsc.Abstract/Blogspot/IBlog.cs
create mode 100644 src/Yavsc.Abstract/Blogspot/IBlogPost.cs
create mode 100644 src/Yavsc.Abstract/Blogspot/IBlogPostPayLoad.cs
diff --git a/src/PostIt.Tests/BlogApiTestFakes.cs b/src/PostIt.Tests/BlogApiTestFakes.cs
index 9ec10f89..755ce105 100644
--- a/src/PostIt.Tests/BlogApiTestFakes.cs
+++ b/src/PostIt.Tests/BlogApiTestFakes.cs
@@ -1,6 +1,7 @@
using PostIt.Models;
using PostIt.Services;
using PostIt.ViewModels;
+using Yavsc.Models;
namespace PostIt.Tests;
diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs
index 1e0afeaf..b5740f2f 100644
--- a/src/PostIt/PostIt/App.axaml.cs
+++ b/src/PostIt/PostIt/App.axaml.cs
@@ -24,7 +24,7 @@ public partial class App : Application
/// binding sink with a cross-thread exception inside
/// DataValidationErrors.SetErrors.
///
- public IServiceProvider? Services { get; private set; }
+ public IServiceProvider? ServiceProvider { get; private set; }
private MainWindow window;
public App()
{
@@ -91,19 +91,17 @@ public partial class App : Application
services.AddSingleton(sessionStatus);
services.AddTransient();
- var provider = services.BuildServiceProvider();
+ ServiceProvider = services.BuildServiceProvider();
// Bind the canonical Settings to the static accessor so any
// code path that can't easily take a constructor parameter
// (designer surfaces, Avalonia data templates) still gets
// the same instance the rest of the app is using. Idempotent:
// re-binding from a second App boot (tests) is a no-op.
- Settings.BindToServiceProvider(provider);
-
- Services = provider;
+ Settings.BindToServiceProvider(ServiceProvider);
DataTemplates.Clear();
- DataTemplates.Add(new ViewLocator(provider));
+ DataTemplates.Add(new ViewLocator(ServiceProvider));
// Wire the Settings singleton onto the SettingsPage singleton
// once, at composition time. The page is registered as a
@@ -113,7 +111,7 @@ public partial class App : Application
// DataContext, and the TwoWay bindings inside the page keep
// mutating the same in-memory Settings instance that the rest
// of the app reads (OidcClientOptions construction, etc.).
- provider.GetRequiredService().DataContext = settings;
+ ServiceProvider.GetRequiredService().DataContext = settings;
// Settings.DarkMode was previously a dead field: it round-
// tripped through the settings file and the SettingsPage
@@ -134,8 +132,8 @@ public partial class App : Application
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
- var homePage = provider.GetRequiredService();
- homePage.DataContext = provider.GetRequiredService();
+ var homePage = ServiceProvider.GetRequiredService();
+ homePage.DataContext = ServiceProvider.GetRequiredService();
window = new MainWindow();
window.SessionBanner.DataContext = sessionStatus;
@@ -155,8 +153,8 @@ public partial class App : Application
{
var w = (MainWindow)((IClassicDesktopStyleApplicationLifetime)ApplicationLifetime!).MainWindow!;
var nav = w.NavRoot;
- var hp = provider.GetRequiredService();
- hp.DataContext = provider.GetRequiredService();
+ var hp = ServiceProvider.GetRequiredService();
+ hp.DataContext = ServiceProvider.GetRequiredService();
_ = nav.PopToRootAsync();
};
@@ -187,7 +185,7 @@ public partial class App : Application
sessionStatus.OpenSettingsRequested += () =>
{
var w = (MainWindow)((IClassicDesktopStyleApplicationLifetime)ApplicationLifetime!).MainWindow!;
- var settingsPage = provider.GetRequiredService();
+ var settingsPage = ServiceProvider.GetRequiredService();
var stack = w.NavRoot.NavigationStack;
if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], settingsPage))
{
@@ -196,13 +194,13 @@ public partial class App : Application
_ = w.NavRoot.PushAsync(settingsPage);
};
- window.Opened += async (_, _) => await BootAsync(provider, api);
+ window.Opened += async (_, _) => await BootAsync(ServiceProvider, api);
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
{
singleView.MainView = new MainWindow
{
- DataContext = provider.GetRequiredService()
+ DataContext = ServiceProvider.GetRequiredService()
};
}
}
@@ -243,8 +241,8 @@ public partial class App : Application
public static async Task PushMainPageAsync()
{
var app = (App)Current;
- var mainVm = app.Services.GetRequiredService();
- var mainPage = app.Services.GetRequiredService();
+ var mainVm = app.ServiceProvider.GetRequiredService();
+ var mainPage = app.ServiceProvider.GetRequiredService();
mainPage.DataContext = mainVm;
await app.window.FindControl("NavRoot").PushAsync(mainPage).ConfigureAwait(true);
}
diff --git a/src/PostIt/PostIt/Models/BlogPost.cs b/src/PostIt/PostIt/Models/BlogPost.cs
index 7867eb02..e62fcea2 100644
--- a/src/PostIt/PostIt/Models/BlogPost.cs
+++ b/src/PostIt/PostIt/Models/BlogPost.cs
@@ -1,16 +1,37 @@
using System;
+using Yavsc.Abstract.Identity;
+using Yavsc.Abstract.Identity.Security;
+using Yavsc.Blogspot;
namespace PostIt.Models;
-public class BlogPost
+public class BlogPost : IBlogPost
{
- public long Id { get; set; }
- public string Title { get; set; } = string.Empty;
- public string? Article { get; set; }
- public string? Photo { get; set; }
- public string? AuthorId { get; set; }
- public DateTime DateCreated { get; set; }
- public string? UserCreated { get; set; }
- public DateTime DateModified { get; set; }
- public string? UserModified { get; set; }
+ public string AuthorId { get; set; }
+
+ public IApplicationUser Author { get; set; }
+
+ public string Article { get; set ; }
+ public string Photo { get; set ; }
+ public long Id { get; set ; }
+ public DateTime DateCreated { get; set ; }
+ public string UserCreated { get; set ; }
+ public DateTime DateModified { get; set ; }
+ public string UserModified { get; set ; }
+ public string Title { get; set ; }
+
+ public bool AuthorizeCircle(long circleId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ICircleAuthorization[] GetACL()
+ {
+ throw new NotImplementedException();
+ }
+
+ public string[] GetTags()
+ {
+ throw new NotImplementedException();
+ }
}
diff --git a/src/PostIt/PostIt/ViewModels/HomePageViewModel.cs b/src/PostIt/PostIt/ViewModels/HomePageViewModel.cs
index c11e396a..876f862c 100644
--- a/src/PostIt/PostIt/ViewModels/HomePageViewModel.cs
+++ b/src/PostIt/PostIt/ViewModels/HomePageViewModel.cs
@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.Input;
+using Microsoft.Extensions.DependencyInjection;
using PostIt;
using PostIt.Services;
namespace PostIt.ViewModels;
@@ -7,6 +8,7 @@ public class HomePageViewModel : ViewModelBase
{
public YavscApiClient Api { get; }
public Settings Settings { get; }
+ public SessionStatusViewModel SessionStatus { get; }
private string _welcomeText = "Welcome to PostIt!";
public string WelcomeText
@@ -18,10 +20,12 @@ public class HomePageViewModel : ViewModelBase
public override bool CanNavigateNext { get => true; protected set => throw new System.NotImplementedException(); }
public override bool CanNavigatePrevious { get => false; protected set => throw new System.NotImplementedException(); }
- public HomePageViewModel(YavscApiClient api, Settings settings)
+ public HomePageViewModel(YavscApiClient api, Settings settings, SessionStatusViewModel sessionStatus)
{
Api = api;
Settings = settings;
+ SessionStatus = sessionStatus;
+
}
public RelayCommand OpenBlogs { get; set; } = new RelayCommand(() => App.PushMainPageAsync());
///
@@ -33,5 +37,8 @@ public class HomePageViewModel : ViewModelBase
/// (thread-safe dispatcher marshalling on PropertyChanged) — a
/// designer-only duplicate instance is therefore harmless.
///
- public HomePageViewModel() : this(null!, new Settings()) { }
+ public HomePageViewModel() : this(null!, new Settings(), new SessionStatusViewModel())
+ {
+
+ }
}
diff --git a/src/PostIt/PostIt/Views/MainPage.axaml.cs b/src/PostIt/PostIt/Views/MainPage.axaml.cs
index 1535d2f4..6a907a01 100644
--- a/src/PostIt/PostIt/Views/MainPage.axaml.cs
+++ b/src/PostIt/PostIt/Views/MainPage.axaml.cs
@@ -28,7 +28,7 @@ public partial class MainPage : ContentPage
// Resolve via the App's DI container so the page gets
// the canonical services (Api client, settings, ...).
var app = Application.Current as App;
- var services = app?.Services;
+ var services = app?.ServiceProvider;
if (services is null) return;
var page = services.GetRequiredService();
diff --git a/src/Yavsc.Abstract/Blogspot/IBlog.cs b/src/Yavsc.Abstract/Blogspot/IBlog.cs
deleted file mode 100644
index 86efc8e3..00000000
--- a/src/Yavsc.Abstract/Blogspot/IBlog.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-using Yavsc.Abstract.Identity;
-
-namespace Yavsc
-{
- public interface IBlogPostPayLoad
- {
- string Article { get; set; }
- string Photo { get; set; }
-
- }
- public interface IBlogPost : IBlogPostPayLoad, ITrackedEntity, IIdentified, ITitle
- {
- string AuthorId { get; set; }
- IApplicationUser Author { get; }
- }
-}
diff --git a/src/Yavsc.Abstract/Blogspot/IBlogPost.cs b/src/Yavsc.Abstract/Blogspot/IBlogPost.cs
new file mode 100644
index 00000000..691e03f2
--- /dev/null
+++ b/src/Yavsc.Abstract/Blogspot/IBlogPost.cs
@@ -0,0 +1,15 @@
+
+
+
+using Yavsc.Abstract.Identity;
+using Yavsc.Abstract.Identity.Security;
+using Yavsc.Interfaces;
+
+namespace Yavsc.Blogspot
+{
+ public interface IBlogPost : IBlogPostPayLoad, ICircleAuthorized, ITaggable, ITrackedEntity, IIdentified, ITitle
+ {
+ string AuthorId { get; set; }
+ IApplicationUser Author { get; }
+ }
+}
diff --git a/src/Yavsc.Abstract/Blogspot/IBlogPostPayLoad.cs b/src/Yavsc.Abstract/Blogspot/IBlogPostPayLoad.cs
new file mode 100644
index 00000000..d8aeb4fe
--- /dev/null
+++ b/src/Yavsc.Abstract/Blogspot/IBlogPostPayLoad.cs
@@ -0,0 +1,9 @@
+namespace Yavsc.Blogspot
+{
+ public interface IBlogPostPayLoad
+ {
+ string Article { get; set; }
+ string Photo { get; set; }
+
+ }
+}
diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs
index 69b37e60..23d71742 100644
--- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs
@@ -1,5 +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;
diff --git a/src/Yavsc.Server/Models/Blog/BlogPost.cs b/src/Yavsc.Server/Models/Blog/BlogPost.cs
index eb58cb62..e1b44603 100644
--- a/src/Yavsc.Server/Models/Blog/BlogPost.cs
+++ b/src/Yavsc.Server/Models/Blog/BlogPost.cs
@@ -3,15 +3,14 @@ using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
using Yavsc.Abstract.Identity;
using Yavsc.Abstract.Identity.Security;
-using Yavsc.Interfaces;
using Yavsc.Models.Access;
using Yavsc.Models.Relationship;
+using Yavsc.Blogspot;
namespace Yavsc.Models.Blog
{
-
- public class BlogPost :
- IBlogPost, ICircleAuthorized, ITaggable
+
+ public class BlogPost : IBlogPost
{
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Display(Name = "Identifiant du post")]
diff --git a/src/Yavsc.Server/Services/BlogSpotService.cs b/src/Yavsc.Server/Services/BlogSpotService.cs
index a8b52d0d..dc02cde2 100644
--- a/src/Yavsc.Server/Services/BlogSpotService.cs
+++ b/src/Yavsc.Server/Services/BlogSpotService.cs
@@ -10,6 +10,7 @@ using Yavsc.Server.Helpers;
using Yavsc.Services;
using Yavsc.ViewModels.Auth;
using Microsoft.AspNetCore.Http;
+using Yavsc.Blogspot;
public class BlogSpotService
{
From 3744d9ae9cc9459bea2cfe57eca9e631547a827b Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Mon, 3 Aug 2026 01:48:15 +0100
Subject: [PATCH 02/24] Enable blogs on connected status
---
src/PostIt/PostIt/Views/HomePage.axaml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/PostIt/PostIt/Views/HomePage.axaml b/src/PostIt/PostIt/Views/HomePage.axaml
index 7eb6dbb3..16e66cc0 100644
--- a/src/PostIt/PostIt/Views/HomePage.axaml
+++ b/src/PostIt/PostIt/Views/HomePage.axaml
@@ -16,6 +16,7 @@
HorizontalAlignment="Center"/>
+ HorizontalAlignment="Center"
+ IsEnabled="{Binding SessionStatus.IsLoggedIn}"/>
From 7d1cca9df0eb204ba587b50bf1d22ff30ea724e9 Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Mon, 3 Aug 2026 02:16:57 +0100
Subject: [PATCH 03/24] build
---
.../Communicating/BlogspotController.cs | 1 +
src/Yavsc.Org/Services/BlogSpotService.cs | 1 +
src/Yavsc.Org/Views/Blogspot/Index.cshtml | 27 ++++++++++---------
src/Yavsc.Org/Views/_ViewImports.cshtml | 1 +
4 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs b/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs
index f59b8b82..de0c3c2d 100644
--- a/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs
+++ b/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs
@@ -7,6 +7,7 @@ using Yavsc.Models.Blog;
using Microsoft.Extensions.Options;
using Yavsc.Server.Exceptions;
using Yavsc.Server.Helpers;
+using Yavsc.Blogspot;
// For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
diff --git a/src/Yavsc.Org/Services/BlogSpotService.cs b/src/Yavsc.Org/Services/BlogSpotService.cs
index 3700e04f..76858c9c 100644
--- a/src/Yavsc.Org/Services/BlogSpotService.cs
+++ b/src/Yavsc.Org/Services/BlogSpotService.cs
@@ -3,6 +3,7 @@ using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Yavsc;
+using Yavsc.Blogspot;
using Yavsc.Models;
using Yavsc.Models.Blog;
using Yavsc.Server.Exceptions;
diff --git a/src/Yavsc.Org/Views/Blogspot/Index.cshtml b/src/Yavsc.Org/Views/Blogspot/Index.cshtml
index 1e7321aa..d5dc27c9 100644
--- a/src/Yavsc.Org/Views/Blogspot/Index.cshtml
+++ b/src/Yavsc.Org/Views/Blogspot/Index.cshtml
@@ -1,3 +1,4 @@
+
@model IEnumerable
@{
ViewBag.Title = "Blogs, l'index";
@@ -43,20 +44,20 @@
Create a new article
}
-
+
@{
int maxTextLen = 75;
foreach (var post in Model) {
-
-
+
+
@post.Title
-
+
@post.Article
@Html.DisplayFor(m => post.Author)
@@ -67,20 +68,20 @@
- @if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded)
+ @if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded)
{
-
Details
+
Details
}
- else
+ else
{
-
Details
+
Details
}
- @if ((await AuthorizationService.AuthorizeAsync(User, post, new EditPermission())).Succeeded)
+ @if ((await AuthorizationService.AuthorizeAsync(User, post, new EditPermission())).Succeeded)
{
-
Edit
-
-
Delete
-
+
Edit
+
+
Delete
+
}
diff --git a/src/Yavsc.Org/Views/_ViewImports.cshtml b/src/Yavsc.Org/Views/_ViewImports.cshtml
index dd88dabc..79811042 100755
--- a/src/Yavsc.Org/Views/_ViewImports.cshtml
+++ b/src/Yavsc.Org/Views/_ViewImports.cshtml
@@ -1,5 +1,6 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Yavsc
+@using Yavsc.Blogspot
@using Yavsc.Models
@using Yavsc.Models.Musical;
@using Yavsc.Models.Drawing;
From c8b05a8950c483ee4844c634304c9b9867255945 Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Mon, 3 Aug 2026 02:35:50 +0100
Subject: [PATCH 04/24] publish android
---
.github/workflows/docker-publish-android.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docker-publish-android.yml b/.github/workflows/docker-publish-android.yml
index 98237136..317cda80 100644
--- a/.github/workflows/docker-publish-android.yml
+++ b/.github/workflows/docker-publish-android.yml
@@ -44,7 +44,7 @@ jobs:
publish-release:
# Uniquement déclenché par un tag v*. Le job apk-deploy tourne en
# parallèle, on partage l'artefact entre jobs.
- if: startsWith(github.ref, 'refs/tags/v')
+ if: startsWith(github.ref, 'refs/tags/')
needs: apk-deploy
runs-on: ubuntu-latest
steps:
From 64547840e4a8e4b814a2eda73eda08dd20032d94 Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Wed, 5 Aug 2026 21:05:14 +0100
Subject: [PATCH 05/24] refacto BlogPost serialization
---
src/Yavsc.Abstract/Blogspot/IBlogPost.cs | 3 +--
.../Identity/Security/ICircleAuthorized.cs | 8 ++++++--
src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs | 4 ++--
src/Yavsc.Blogs.Tests/BlogApiTests.cs | 3 ++-
src/Yavsc.Blogs/Controllers/BlogApiController.cs | 4 ++--
src/Yavsc.Org.Tests/appsettings.json | 1 +
src/Yavsc.Org/Views/Blogspot/Index.cshtml | 12 ++++++------
src/Yavsc.Server/Models/Blog/BlogPost.cs | 4 ++--
8 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/src/Yavsc.Abstract/Blogspot/IBlogPost.cs b/src/Yavsc.Abstract/Blogspot/IBlogPost.cs
index 691e03f2..5287685d 100644
--- a/src/Yavsc.Abstract/Blogspot/IBlogPost.cs
+++ b/src/Yavsc.Abstract/Blogspot/IBlogPost.cs
@@ -7,9 +7,8 @@ using Yavsc.Interfaces;
namespace Yavsc.Blogspot
{
- public interface IBlogPost : IBlogPostPayLoad, ICircleAuthorized, ITaggable, ITrackedEntity, IIdentified, ITitle
+ public interface IBlogPost : IBlogPostPayLoad, ICircleAuthorized, ITrackedEntity, ITitle
{
- string AuthorId { get; set; }
IApplicationUser Author { get; }
}
}
diff --git a/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs
index cc108d2e..25c21961 100644
--- a/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs
+++ b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs
@@ -1,10 +1,14 @@
+using Yavsc.Interfaces;
+
namespace Yavsc.Abstract.Identity.Security
{
- public interface ICircleAuthorized
+ public interface ICircleAuthorized : ITaggable
{
- long Id { get; set; }
+
string AuthorId { get; }
+
bool AuthorizeCircle(long circleId);
+
ICircleAuthorization [] GetACL();
}
diff --git a/src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs b/src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs
index 89a51d30..eb325b6f 100644
--- a/src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs
+++ b/src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs
@@ -1,9 +1,9 @@
namespace Yavsc.Interfaces
{
- public interface ITaggable
+ public interface ITaggable : IIdentified
{
string [] GetTags();
K Id { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
index fd64555a..35d67fbe 100644
--- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs
+++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
@@ -239,7 +239,8 @@ public sealed class BlogApiTests : IClassFixture
// The list should now be empty.
var listResponse = await http.GetAsync("/api/v1/blog");
- using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync());
+ String response = await listResponse.Content.ReadAsStringAsync();
+ using var doc = JsonDocument.Parse(response);
Assert.Equal(0, doc.RootElement.GetArrayLength());
}
diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs
index 23d71742..ac7b59df 100644
--- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs
@@ -22,9 +22,9 @@ namespace Yavsc.Blogs.Controllers
// GET: api/BlogApi
[HttpGet]
- public async Task> GetBlogspot(int start = 0, int take = 25)
+ public async Task> GetBlogspot(int start = 0, int take = 25)
{
- return await blogSpotService.Index(User, null, start, take);
+ return (await blogSpotService.Index(User, null, start, take)).Cast();
}
// GET: api/BlogApi/5
diff --git a/src/Yavsc.Org.Tests/appsettings.json b/src/Yavsc.Org.Tests/appsettings.json
index 5f353cd8..ef95fef6 100644
--- a/src/Yavsc.Org.Tests/appsettings.json
+++ b/src/Yavsc.Org.Tests/appsettings.json
@@ -1,6 +1,7 @@
{
"Site": {
"Authority": "https://localhost:5101",
+ "Audience": ["blogs"],
"Title": "Yavsc dev",
"Slogan": "Yavsc : WIP.",
"Banner": "/images/yavsc.png",
diff --git a/src/Yavsc.Org/Views/Blogspot/Index.cshtml b/src/Yavsc.Org/Views/Blogspot/Index.cshtml
index d5dc27c9..52cf3b88 100644
--- a/src/Yavsc.Org/Views/Blogspot/Index.cshtml
+++ b/src/Yavsc.Org/Views/Blogspot/Index.cshtml
@@ -52,19 +52,19 @@
-
+
@post.Title
-
+
@post.Article
@Html.DisplayFor(m => post.Author)
posté le @post.DateCreated.ToString("dddd d MMM yyyy à H:mm")
@if ((post.DateModified - post.DateCreated).Minutes > 0){
@:- Modifié le @post.DateModified.ToString("dddd d MMM yyyy à H:mm")
- })
+ }
@@ -74,13 +74,13 @@
}
else
{
-
Details
+
Details
}
@if ((await AuthorizationService.AuthorizeAsync(User, post, new EditPermission())).Succeeded)
{
-
Edit
+
Edit
-
Delete
+
Delete
}
diff --git a/src/Yavsc.Server/Models/Blog/BlogPost.cs b/src/Yavsc.Server/Models/Blog/BlogPost.cs
index e1b44603..442cbb1f 100644
--- a/src/Yavsc.Server/Models/Blog/BlogPost.cs
+++ b/src/Yavsc.Server/Models/Blog/BlogPost.cs
@@ -35,7 +35,7 @@ namespace Yavsc.Models.Blog
public string? AuthorId { get; set; }
[Display(Name = "Auteur")]
- public virtual ApplicationUser? Author { set; get; }
+ public virtual ApplicationUser Author { set; get; }
[Display(Name = "Date de création")]
@@ -95,6 +95,6 @@ namespace Yavsc.Models.Blog
[InverseProperty("Post")]
public virtual List
Comments { get; set; }
- IApplicationUser IBlogPost.Author { get => this.Author; }
+ IApplicationUser IBlogPost.Author => Author;
}
}
From cd03b04755cffda61179cded29ff74759c3a50f8 Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Wed, 5 Aug 2026 21:11:22 +0100
Subject: [PATCH 06/24] re-refacto BlogPost serialization
---
src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs | 2 --
src/Yavsc.Blogs/Controllers/BlogApiController.cs | 4 ++--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs b/src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs
index eb325b6f..bb4020a8 100644
--- a/src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs
+++ b/src/Yavsc.Abstract/Interfaces/Models/ITaggable.cs
@@ -3,7 +3,5 @@ namespace Yavsc.Interfaces
public interface ITaggable : IIdentified
{
string [] GetTags();
-
- K Id { get; }
}
}
diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs
index ac7b59df..23d71742 100644
--- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs
@@ -22,9 +22,9 @@ namespace Yavsc.Blogs.Controllers
// GET: api/BlogApi
[HttpGet]
- public async Task> GetBlogspot(int start = 0, int take = 25)
+ public async Task> GetBlogspot(int start = 0, int take = 25)
{
- return (await blogSpotService.Index(User, null, start, take)).Cast();
+ return await blogSpotService.Index(User, null, start, take);
}
// GET: api/BlogApi/5
From 44b391d496de0aa7bbb3521bbba13c5c1526791b Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Mon, 10 Aug 2026 18:12:59 +0100
Subject: [PATCH 07/24] Activity protection
---
Directory.Build.props | 1 +
.../Business/ActivityApiController.cs | 3 +-
.../NativeConfidentialController.cs | 8 ++---
src/Yavsc.Blogs.Tests/BlogApiTests.cs | 32 +++++++++++++++++++
src/Yavsc.Org/Extensions/HostingExtensions.cs | 3 ++
.../Services/GoogleApis/CalendarManager.cs | 2 +-
6 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/Directory.Build.props b/Directory.Build.props
index 873845c4..aec8c990 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -11,5 +11,6 @@
from without conflicting names.
-->
true
+ NU1701, NU1901, NU1902
diff --git a/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs b/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs
index f6b215e7..d2da2ea7 100644
--- a/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs
@@ -15,7 +15,6 @@ namespace Yavsc.Controllers
{
[Produces("application/json")]
[Route("api/activity")]
- [AllowAnonymous]
public class ActivityApiController : Controller
{
private ApplicationDbContext _context;
@@ -88,7 +87,7 @@ namespace Yavsc.Controllers
}
// POST: api/ActivityApi
- [HttpPost,Authorize("AdministratorOnly")]
+ [HttpPost, Authorize("AdministratorOnly")]
public async Task PostActivity([FromBody] Activity activity)
{
if (!ModelState.IsValid)
diff --git a/src/Yavsc.Api/Controllers/NativeConfidentialController.cs b/src/Yavsc.Api/Controllers/NativeConfidentialController.cs
index 01cd8478..4e771830 100644
--- a/src/Yavsc.Api/Controllers/NativeConfidentialController.cs
+++ b/src/Yavsc.Api/Controllers/NativeConfidentialController.cs
@@ -1,15 +1,15 @@
-using System;
-using System.Linq;
+
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using Yavsc.Helpers;
+
using Yavsc.Models;
using Yavsc.Models.Identity;
using Yavsc.Server.Helpers;
+#nullable enable
+
[Authorize, Route("~/api/gcm")]
public class NativeConfidentialController : Controller
{
diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
index 35d67fbe..bf0758c6 100644
--- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs
+++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
@@ -148,6 +148,38 @@ public sealed class BlogApiTests : IClassFixture
Assert.Equal(created.Id, doc.RootElement[0].GetProperty("id").GetInt64());
}
+ [Fact]
+ public async Task PostBlog_sets_AuthorId_on_created_post_and_list_entry()
+ {
+ ResetDatabase();
+ using var http = NewClient(subject: "tester");
+
+ var draft = new BlogPost
+ {
+ Id = 0,
+ Title = "Billet avec auteur",
+ AuthorId = "payload-attacker",
+ Article = "Contenu de test.",
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ };
+
+ var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft);
+ Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode);
+
+ var created = await postResponse.Content.ReadFromJsonAsync();
+ Assert.NotNull(created);
+ Assert.Equal("tester", created!.AuthorId);
+
+ var listResponse = await http.GetAsync("/api/v1/blog");
+ Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode);
+
+ using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync());
+ Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind);
+ Assert.Equal(1, doc.RootElement.GetArrayLength());
+ Assert.Equal("tester", doc.RootElement[0].GetProperty("authorId").GetString());
+ }
+
[Fact]
public async Task GetBlog_returns_401_when_no_token_is_provided()
{
diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs
index b3f90639..0cae23a7 100644
--- a/src/Yavsc.Org/Extensions/HostingExtensions.cs
+++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs
@@ -1189,6 +1189,8 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;");
}
}
+#nullable enable
+
static void LoadGoogleConfig(IConfigurationRoot configuration)
{
string? googleClientFile = configuration["Authentication:Google:GoogleWebClientJson"];
@@ -1204,6 +1206,7 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;");
Config.GServiceAccount = JsonConvert.DeserializeObject(safile.OpenText().ReadToEnd());
}
}
+#nullable disable
public static IApplicationBuilder ConfigureFileServerApp(this IApplicationBuilder app,
bool enableDirectoryBrowsing = false)
diff --git a/src/Yavsc.Server/Services/GoogleApis/CalendarManager.cs b/src/Yavsc.Server/Services/GoogleApis/CalendarManager.cs
index 00f5a9b2..f91b3393 100644
--- a/src/Yavsc.Server/Services/GoogleApis/CalendarManager.cs
+++ b/src/Yavsc.Server/Services/GoogleApis/CalendarManager.cs
@@ -197,7 +197,7 @@ namespace Yavsc.Services
if (credential.IsCreateScopedRequired)
{
credential = credential.CreateScoped(scopesCalendar);
- }/*
+ }/*
var credential = await GoogleHelpers.GetCredentialForApi(new string [] { scopeCalendar });
if (credential.IsCreateScopedRequired)
{
From 0d3fbf22c3e8e474b3464a39330522f80162781c Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Mon, 10 Aug 2026 18:34:01 +0100
Subject: [PATCH 08/24] GetUserId_reads_NameIdentifier_when_sub_was_mapped
---
.../BlogApiMappedClaimsTests.cs | 156 ++++++++++++++++++
src/Yavsc.Blogs.Tests/BlogApiTests.cs | 14 ++
.../JwtClaimMappingCollection.cs | 8 +
.../MappedClaimsBlogsWebServerFixture.cs | 109 ++++++++++++
src/Yavsc.Server/Helpers/UserHelpers.cs | 4 +-
5 files changed, 290 insertions(+), 1 deletion(-)
create mode 100644 src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs
create mode 100644 src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs
create mode 100644 src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs
diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs
new file mode 100644
index 00000000..f02a1b99
--- /dev/null
+++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs
@@ -0,0 +1,156 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Net.Http.Json;
+using System.Security.Claims;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
+using Yavsc.Models;
+using Yavsc.Models.Blog;
+using Yavsc.Tests.Shared;
+
+namespace Yavsc.Blogs.Tests;
+
+[Collection("JwtClaimMapping")]
+public sealed class BlogApiMappedClaimsTests : IClassFixture
+{
+ private readonly MappedClaimsBlogsWebServerFixture _fixture;
+
+ public BlogApiMappedClaimsTests(MappedClaimsBlogsWebServerFixture fixture)
+ {
+ _fixture = fixture;
+ }
+
+ private void ResetDatabase()
+ {
+ using var scope = _fixture.Services.CreateScope();
+ var db = scope.ServiceProvider.GetRequiredService();
+ db.Database.EnsureDeleted();
+ db.Database.EnsureCreated();
+ }
+
+ private HttpClient NewClient(string subject = "tester")
+ {
+ var http = new HttpClient
+ {
+ BaseAddress = new Uri(_fixture.Addresses.First())
+ };
+ http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
+ "Bearer",
+ IssueMappedClaimsToken(subject));
+ return http;
+ }
+
+ private static string IssueMappedClaimsToken(string subject)
+ {
+ var now = DateTime.UtcNow;
+ var claims = new List
+ {
+ new("sub", subject),
+ new("scope", "blogs"),
+ };
+
+ var token = new JwtSecurityToken(
+ issuer: TestTokenIssuer.Issuer,
+ audience: TestTokenIssuer.Audience,
+ claims: claims,
+ notBefore: now,
+ expires: now.AddHours(1),
+ signingCredentials: new SigningCredentials(
+ TestTokenIssuer.SigningKey,
+ SecurityAlgorithms.HmacSha256));
+
+ return new JwtSecurityTokenHandler().WriteToken(token);
+ }
+
+ [Fact]
+ public async Task PostBlog_with_mapped_sub_claim_sets_AuthorId_from_authenticated_user()
+ {
+ ResetDatabase();
+ using var http = NewClient(subject: "mapped-user");
+
+ var draft = new BlogPost
+ {
+ Id = 0,
+ Title = "Billet JWT remappe",
+ AuthorId = "payload-attacker",
+ Article = "Contenu de test.",
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ };
+
+ var response = await http.PostAsJsonAsync("/api/v1/blog", draft);
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+
+ var created = await response.Content.ReadFromJsonAsync();
+ Assert.NotNull(created);
+ Assert.Equal("mapped-user", created!.AuthorId);
+ }
+
+ [Fact]
+ public async Task PutBlog_with_mapped_sub_claim_allows_owner_to_update()
+ {
+ ResetDatabase();
+ using var http = NewClient(subject: "mapped-owner");
+
+ var createdResponse = await http.PostAsJsonAsync("/api/v1/blog", new BlogPost
+ {
+ Id = 0,
+ Title = "Billet à modifier",
+ AuthorId = "payload-attacker",
+ Article = "Contenu initial.",
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ });
+
+ Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode);
+ var created = await createdResponse.Content.ReadFromJsonAsync();
+ Assert.NotNull(created);
+
+ var updateResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost
+ {
+ Id = created.Id,
+ Title = "Billet modifié",
+ AuthorId = created.AuthorId,
+ Article = "Contenu mis à jour.",
+ DateCreated = created.DateCreated,
+ DateModified = DateTime.UtcNow
+ });
+
+ Assert.Equal(HttpStatusCode.NoContent, updateResponse.StatusCode);
+ }
+
+ [Fact]
+ public async Task PutBlog_with_mapped_sub_claim_rejects_non_owner()
+ {
+ ResetDatabase();
+ using var ownerHttp = NewClient(subject: "mapped-owner");
+
+ var createdResponse = await ownerHttp.PostAsJsonAsync("/api/v1/blog", new BlogPost
+ {
+ Id = 0,
+ Title = "Billet protégé",
+ AuthorId = "payload-attacker",
+ Article = "Contenu initial.",
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ });
+
+ Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode);
+ var created = await createdResponse.Content.ReadFromJsonAsync();
+ Assert.NotNull(created);
+
+ using var otherHttp = NewClient(subject: "mapped-other");
+ var updateResponse = await otherHttp.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost
+ {
+ Id = created.Id,
+ Title = "Tentative de modification",
+ AuthorId = created.AuthorId,
+ Article = "Contenu non autorisé.",
+ DateCreated = created.DateCreated,
+ DateModified = DateTime.UtcNow
+ });
+
+ Assert.Equal(HttpStatusCode.Unauthorized, updateResponse.StatusCode);
+ }
+}
diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
index bf0758c6..7e725e4f 100644
--- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs
+++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
@@ -1,10 +1,12 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
+using System.Security.Claims;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Yavsc.Models;
using Yavsc.Models.Blog;
+using Yavsc.Server.Helpers;
using Yavsc.Tests.Shared;
namespace Yavsc.Blogs.Tests;
@@ -20,6 +22,7 @@ namespace Yavsc.Blogs.Tests;
/// header (or sending a token signed with the wrong key) gets a
/// 401 back from the framework.
///
+[Collection("JwtClaimMapping")]
public sealed class BlogApiTests : IClassFixture
{
private readonly BlogsWebServerFixture _fixture;
@@ -180,6 +183,17 @@ public sealed class BlogApiTests : IClassFixture
Assert.Equal("tester", doc.RootElement[0].GetProperty("authorId").GetString());
}
+ [Fact]
+ public void GetUserId_reads_NameIdentifier_when_sub_was_mapped()
+ {
+ var principal = new ClaimsPrincipal(
+ new ClaimsIdentity(
+ [new Claim(ClaimTypes.NameIdentifier, "tester")],
+ authenticationType: "Bearer"));
+
+ Assert.Equal("tester", principal.GetUserId());
+ }
+
[Fact]
public async Task GetBlog_returns_401_when_no_token_is_provided()
{
diff --git a/src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs b/src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs
new file mode 100644
index 00000000..e141c0f3
--- /dev/null
+++ b/src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs
@@ -0,0 +1,8 @@
+using Xunit;
+
+namespace Yavsc.Blogs.Tests;
+
+[CollectionDefinition("JwtClaimMapping", DisableParallelization = true)]
+public sealed class JwtClaimMappingCollection
+{
+}
diff --git a/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs
new file mode 100644
index 00000000..c9d95774
--- /dev/null
+++ b/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs
@@ -0,0 +1,109 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
+using Yavsc.Blogs.Controllers;
+using Yavsc.Models;
+using Yavsc.Services;
+using Yavsc.Tests.Shared;
+
+namespace Yavsc.Blogs.Tests;
+
+///
+/// Dedicated integration-test host that mirrors the production JWT
+/// remapping behavior: MapInboundClaims remains enabled and the
+/// default inbound map rewrites "sub" to ClaimTypes.NameIdentifier.
+/// This is the closest in-process reproduction of the production
+/// authentication surface for the blog API.
+///
+public sealed class MappedClaimsBlogsWebServerFixture : IDisposable
+{
+ private readonly InMemoryDatabaseRoot _inMemoryRoot = new();
+ private readonly Dictionary _savedInboundMap;
+ private readonly WebApplication _app;
+
+ public MappedClaimsBlogsWebServerFixture()
+ {
+ _savedInboundMap = new Dictionary(JwtSecurityTokenHandler.DefaultInboundClaimTypeMap);
+ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap["sub"] = ClaimTypes.NameIdentifier;
+
+ var builder = WebApplication.CreateBuilder();
+ builder.WebHost.UseUrls("http://127.0.0.1:5104");
+
+ builder.Services.AddDbContext(opt =>
+ opt.UseInMemoryDatabase("Yavsc.Blogs.Tests.MappedClaims", _inMemoryRoot));
+
+ builder.Services.AddSingleton(new NoopFileSystemAuthManager());
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddControllers()
+ .AddApplicationPart(typeof(BlogApiController).Assembly);
+ builder.Services.AddAuthorization(opt =>
+ {
+ opt.AddPolicy("BlogScope", policy =>
+ {
+ policy.RequireAuthenticatedUser()
+ .RequireClaim("scope", "blogs");
+ });
+ });
+ builder.Services.AddAuthentication("Bearer")
+ .AddJwtBearer("Bearer", options =>
+ {
+ options.IncludeErrorDetails = true;
+ options.MapInboundClaims = true;
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidIssuer = TestTokenIssuer.Issuer,
+ ValidateAudience = false,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = TestTokenIssuer.SigningKey,
+ RoleClaimType = YavscConstants.RoleClaimType,
+ NameClaimType = YavscConstants.NameClaimType,
+ };
+ });
+
+ _app = builder.Build();
+ _app.UseRouting();
+ _app.UseAuthentication();
+ _app.UseAuthorization();
+ _app.MapControllers();
+ _app.StartAsync().GetAwaiter().GetResult();
+
+ Addresses = ["http://127.0.0.1:5104"];
+ Services = _app.Services;
+ }
+
+ public IReadOnlyList Addresses { get; }
+
+ public IServiceProvider Services { get; }
+
+ public void Dispose()
+ {
+ _app.StopAsync().GetAwaiter().GetResult();
+ _app.DisposeAsync().AsTask().GetAwaiter().GetResult();
+
+ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
+ foreach (var kvp in _savedInboundMap)
+ {
+ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[kvp.Key] = kvp.Value;
+ }
+ }
+
+ private sealed class NoopFileSystemAuthManager : IFileSystemAuthManager
+ {
+ public FileAccessRight GetFilePathAccess(System.Security.Claims.ClaimsPrincipal user, string fileRelativePath)
+ => FileAccessRight.None;
+
+ public void SetAccess(long circleId, string normalizedFullPath, FileAccessRight access)
+ {
+ }
+ }
+}
diff --git a/src/Yavsc.Server/Helpers/UserHelpers.cs b/src/Yavsc.Server/Helpers/UserHelpers.cs
index 105c1beb..c3ee708d 100644
--- a/src/Yavsc.Server/Helpers/UserHelpers.cs
+++ b/src/Yavsc.Server/Helpers/UserHelpers.cs
@@ -32,7 +32,9 @@ namespace Yavsc.Server.Helpers
public static string GetUserId(this ClaimsPrincipal user)
{
- return user.FindFirstValue("sub");
+ return user.FindFirstValue("sub")
+ ?? user.FindFirstValue(ClaimTypes.NameIdentifier)
+ ?? user.FindFirstValue("nameid");
}
public static string GetUserName(this ClaimsPrincipal user)
From 0fd9e40d67db41055f16c192e930aafa0bad2fe5 Mon Sep 17 00:00:00 2001
From: Paul Schneider
Date: Mon, 10 Aug 2026 21:48:03 +0100
Subject: [PATCH 09/24] Fix blog comment endpoint path and add regression test
---
src/Yavsc.Blogs.Tests/BlogApiTests.cs | 36 +++++++++++++++++++
.../Communicating/BlogspotController.cs | 2 +-
.../ViewComponents/CommentViewComponent.cs | 2 +-
src/Yavsc.Org/Views/Blogspot/Details.cshtml | 6 ++--
4 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
index 7e725e4f..cc7aaec8 100644
--- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs
+++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
@@ -183,6 +183,42 @@ public sealed class BlogApiTests : IClassFixture
Assert.Equal("tester", doc.RootElement[0].GetProperty("authorId").GetString());
}
+ [Fact]
+ public async Task PostBlogComment_returns_201_for_existing_post()
+ {
+ ResetDatabase();
+ using var http = NewClient(subject: "tester");
+
+ var draft = new BlogPost
+ {
+ Id = 0,
+ Title = "Billet commentable",
+ AuthorId = "payload-attacker",
+ Article = "Contenu de test.",
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ };
+
+ var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft);
+ Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode);
+
+ var createdPost = await postResponse.Content.ReadFromJsonAsync();
+ Assert.NotNull(createdPost);
+
+ var commentResponse = await http.PostAsJsonAsync("/api/v1/blogcomments", new
+ {
+ Article = "Premier commentaire",
+ ReceiverId = createdPost!.Id
+ });
+
+ Assert.Equal(HttpStatusCode.Created, commentResponse.StatusCode);
+
+ using var doc = JsonDocument.Parse(await commentResponse.Content.ReadAsStringAsync());
+ Assert.True(doc.RootElement.TryGetProperty("id", out var id));
+ Assert.True(id.GetInt64() > 0);
+ Assert.True(doc.RootElement.TryGetProperty("dateCreated", out _));
+ }
+
[Fact]
public void GetUserId_reads_NameIdentifier_when_sub_was_mapped()
{
diff --git a/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs b/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs
index de0c3c2d..8e2c9e5b 100644
--- a/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs
+++ b/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs
@@ -71,7 +71,7 @@ namespace Yavsc.Org.Controllers
try
{
var blog = await blogSpotService.Details(User, id.Value);
- ViewBag.apicmtctlr = "/api/blogcomments";
+ ViewBag.apicmtctlr = "/api/v1/blogcomments";
ViewBag.moderatoFlag = User.IsInMsRole(YavscConstants.BlogModeratorGroupName);
return View(blog);
diff --git a/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs b/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs
index 6752de94..62d3bb7a 100644
--- a/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs
+++ b/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs
@@ -23,7 +23,7 @@ namespace Yavsc.ViewComponents
var comment = await context.Comment.Include(c=>c.Children).FirstOrDefaultAsync(c => c.Id==id);
if (comment == null)
throw new InvalidOperationException();
- ViewBag.apictlr = "/api/blogcomments";
+ ViewBag.apictlr = "/api/v1/blogcomments";
return View("Default", comment);
}
diff --git a/src/Yavsc.Org/Views/Blogspot/Details.cshtml b/src/Yavsc.Org/Views/Blogspot/Details.cshtml
index d7d5a791..dc0bea8a 100644
--- a/src/Yavsc.Org/Views/Blogspot/Details.cshtml
+++ b/src/Yavsc.Org/Views/Blogspot/Details.cshtml
@@ -7,7 +7,7 @@