yavsc/src/Yavsc.Api.Client/BlogApiClient.cs

73 lines
3.1 KiB
C#
Raw Normal View History

2026-06-10 00:23:17 +01:00
using System;
using System.Collections.Generic;
using System.Net.Http;
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
using System.Threading;
2026-06-10 00:23:17 +01:00
using System.Threading.Tasks;
refactor(model): move BlogPost DTO from PostIt.Models to Yavsc.Blogspot BlogPost is shared between the server (Yavsc.Server/Models/Blog/ BlogPost.cs is the EF entity) and any client that talks to the blogs API. Keeping the client-side DTO in PostIt.Models made sense when there was only one consumer; now that the Yavsc.Api.Client project is about to host BlogApiClient alongside CircleApiClient and BlogAclApiClient, the DTO has to live in a layer both the client project and PostIt can reference without inverting the dependency. Yavsc.Abstract is the existing home for cross-tier interfaces and DTOs (IBlogPost, IBlogPostPayLoad, IApplicationUser). Yavsc.Blogspot is the sub-namespace already used by the matching interface, so the new concrete class follows. Why not move Circle and CircleAuthorizationToBlogPost at the same time? Both depend on the concrete ApplicationUser class (via the Owner and Target/Allowed navigation properties) which lives in Yavsc.Server. Moving them would mean either dragging ApplicationUser into the abstract layer (huge blast radius — auth, billing, chat, etc.) or weakening the navigation properties (breaks EF Core shaping). They're staying where they are; the new Yavsc.Api.Client will get DTO counterparts instead. Updated call sites: - 4 .cs files: replace 'using PostIt.Models;' with 'using Yavsc.Blogspot;' where the file was actually using BlogPost. Files that only used SignaturePadData keep their 'using PostIt.Models;' — that type stays put. - 1 .axaml file: xmlns:models="using:PostIt.Models" -> xmlns:models="using:Yavsc.Blogspot" (one DataTemplate for the post list in MainPage). Build + tests green (51/51).
2026-08-17 23:45:45 +01:00
using Yavsc.Blogspot;
2026-06-10 00:23:17 +01:00
2026-08-17 23:50:35 +01:00
namespace Yavsc.Api.Client;
2026-06-10 00:23:17 +01:00
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
/// <summary>
/// High-level client for the Blog subsystem of the Yavsc API
/// (deployed at <c>https://blogs.pschneider.fr</c>). All transport
/// concerns — base URL, JSON serialisation, Bearer auth, silent
/// refresh on 401, request body shaping — are delegated to
2026-08-17 23:50:35 +01:00
/// <see cref="YavscApiClient"/>, which lives in the consuming
/// application (PostIt). This class is a thin DTO↔path mapper,
/// nothing more.
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
///
/// <para><b>URL convention.</b> <see cref="YavscApiClient"/>'s
/// <c>BaseAddress</c> already terminates with <c>/api/v1/</c>
/// (see <c>Settings.ApiUrl</c>). The path prefix below is
/// therefore <i>relative</i> to that version segment: a prefix of
/// <c>"blog"</c> resolves to <c>…/api/v1/blog</c>, which matches
/// the <c>[Route(APIPrefix + "/blog")]</c> attribute on
/// <c>Yavsc.Blogs.Controllers.BlogApiController</c>. Do not
/// re-include the <c>api/</c> segment here — that produced 404s
/// in the past (see commit "PostIt: fix blog API double-prefix").</para>
///
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
/// The class is intentionally non-IDisposable: it does not own the
/// <see cref="YavscApiClient"/> it depends on. Lifetimes are managed
/// by the consumer (typically a singleton service registered with
/// the application).
/// </summary>
public sealed class BlogApiClient
2026-06-10 00:23:17 +01:00
{
private const string DefaultPathPrefix = "blog";
2026-06-10 00:23:17 +01:00
2026-08-17 23:50:35 +01:00
private readonly IYavscApiClient _api;
private readonly Uri _baseAddress;
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
private readonly string _pathPrefix;
2026-06-10 00:23:17 +01:00
2026-08-17 23:50:35 +01:00
public BlogApiClient(IYavscApiClient api, string blogsBaseAddress, string pathPrefix = DefaultPathPrefix)
2026-06-10 00:23:17 +01:00
{
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
_api = api ?? throw new ArgumentNullException(nameof(api));
2026-08-17 23:50:35 +01:00
if (string.IsNullOrEmpty(blogsBaseAddress))
throw new ArgumentException("Base address is required.", nameof(blogsBaseAddress));
2026-08-17 23:50:35 +01:00
// e.g. "https://blogs.pschneider.fr/api/v1/" — keep the
// trailing slash so relative paths ("posts") resolve correctly.
2026-08-17 23:50:35 +01:00
_baseAddress = new Uri(blogsBaseAddress);
api.Http.BaseAddress = _baseAddress;
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
_pathPrefix = pathPrefix?.TrimStart('/') ?? DefaultPathPrefix;
2026-06-10 00:23:17 +01:00
}
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
public Task<List<BlogPost>> GetPostsAsync(int start = 0, int take = 25, CancellationToken ct = default)
=> _api.CallAsync<List<BlogPost>>(
HttpMethod.Get,
$"{_pathPrefix}?start={start}&take={take}",
ct: ct);
2026-06-10 00:23:17 +01:00
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
public Task<BlogPost?> GetPostAsync(long id, CancellationToken ct = default)
=> _api.CallAsync<BlogPost?>(HttpMethod.Get, $"{_pathPrefix}/{id}", ct: ct);
2026-06-10 00:23:17 +01:00
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
public Task<BlogPost?> CreatePostAsync(BlogPost post, CancellationToken ct = default)
=> _api.CallAsync<BlogPost?>(HttpMethod.Post, _pathPrefix, body: post, ct: ct);
2026-06-10 00:23:17 +01:00
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
public Task UpdatePostAsync(long id, BlogPost post, CancellationToken ct = default)
=> _api.CallAsync(HttpMethod.Put, $"{_pathPrefix}/{id}", body: post, ct: ct);
2026-06-10 00:23:17 +01:00
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
public Task DeletePostAsync(long id, CancellationToken ct = default)
=> _api.CallAsync(HttpMethod.Delete, $"{_pathPrefix}/{id}", ct: ct);
2026-06-10 00:23:17 +01:00
}