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;
|
2026-08-17 23:45:45 +01:00
|
|
|
using Yavsc.Blogspot;
|
2026-06-10 00:23:17 +01:00
|
|
|
|
feat(api-client): add Yavsc.Api.Client with Blog + Circle + BlogAcl clients
Creates the high-level HTTP client library the PostIt UI will
consume to manage blog posts, circles, and per-post ACLs.
Clients in this commit:
- BlogApiClient (moved from PostIt/Services; same public surface,
now depends on IYavscApiClient instead of the concrete class).
- CircleApiClient (new): GET/POST/PUT/DELETE /api/circle. Takes
the blogs base URL explicitly in its constructor so it doesn't
need to know about PostIt's Settings type.
- BlogAclApiClient (new): GET/POST/PUT/DELETE /api/blogacl.
Same conventions as CircleApiClient.
DTOs (Yavsc.Api.Client.Dtos):
- CircleDto: id, name, ownerId, public. Stops short of the
navigation properties on the server-side Circle (Owner,
Members), which depend on ApplicationUser and other server
types we don't want to drag into the client.
- CircleAuthorizationDto: circleId, blogPostId, comment. Same
reason: the server entity has Target and Allowed navigation
properties the client never needs.
The clients now require the caller to pass the blogs base URL
explicitly in the constructor (previously the BlogApiClient
sniffed it off YavscApiClient.Settings.BlogsApiUrl, but that
field is PostIt-specific). The one production call site
(App.axaml.cs) and four test call sites are updated to pass
the URL.
Build + 51/51 tests green. The IYavscApiClient abstraction was
landed in the previous commit so this one could be a pure
addition + relocation.
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
|
feat(api-client): add Yavsc.Api.Client with Blog + Circle + BlogAcl clients
Creates the high-level HTTP client library the PostIt UI will
consume to manage blog posts, circles, and per-post ACLs.
Clients in this commit:
- BlogApiClient (moved from PostIt/Services; same public surface,
now depends on IYavscApiClient instead of the concrete class).
- CircleApiClient (new): GET/POST/PUT/DELETE /api/circle. Takes
the blogs base URL explicitly in its constructor so it doesn't
need to know about PostIt's Settings type.
- BlogAclApiClient (new): GET/POST/PUT/DELETE /api/blogacl.
Same conventions as CircleApiClient.
DTOs (Yavsc.Api.Client.Dtos):
- CircleDto: id, name, ownerId, public. Stops short of the
navigation properties on the server-side Circle (Owner,
Members), which depend on ApplicationUser and other server
types we don't want to drag into the client.
- CircleAuthorizationDto: circleId, blogPostId, comment. Same
reason: the server entity has Target and Allowed navigation
properties the client never needs.
The clients now require the caller to pass the blogs base URL
explicitly in the constructor (previously the BlogApiClient
sniffed it off YavscApiClient.Settings.BlogsApiUrl, but that
field is PostIt-specific). The one production call site
(App.axaml.cs) and four test call sites are updated to pass
the URL.
Build + 51/51 tests green. The IYavscApiClient abstraction was
landed in the previous commit so this one could be a pure
addition + relocation.
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
|
|
|
///
|
2026-07-06 21:03:10 +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
|
|
|
{
|
2026-07-06 20:58:58 +01:00
|
|
|
private const string DefaultPathPrefix = "blog";
|
2026-06-10 00:23:17 +01:00
|
|
|
|
feat(api-client): add Yavsc.Api.Client with Blog + Circle + BlogAcl clients
Creates the high-level HTTP client library the PostIt UI will
consume to manage blog posts, circles, and per-post ACLs.
Clients in this commit:
- BlogApiClient (moved from PostIt/Services; same public surface,
now depends on IYavscApiClient instead of the concrete class).
- CircleApiClient (new): GET/POST/PUT/DELETE /api/circle. Takes
the blogs base URL explicitly in its constructor so it doesn't
need to know about PostIt's Settings type.
- BlogAclApiClient (new): GET/POST/PUT/DELETE /api/blogacl.
Same conventions as CircleApiClient.
DTOs (Yavsc.Api.Client.Dtos):
- CircleDto: id, name, ownerId, public. Stops short of the
navigation properties on the server-side Circle (Owner,
Members), which depend on ApplicationUser and other server
types we don't want to drag into the client.
- CircleAuthorizationDto: circleId, blogPostId, comment. Same
reason: the server entity has Target and Allowed navigation
properties the client never needs.
The clients now require the caller to pass the blogs base URL
explicitly in the constructor (previously the BlogApiClient
sniffed it off YavscApiClient.Settings.BlogsApiUrl, but that
field is PostIt-specific). The one production call site
(App.axaml.cs) and four test call sites are updated to pass
the URL.
Build + 51/51 tests green. The IYavscApiClient abstraction was
landed in the previous commit so this one could be a pure
addition + relocation.
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
|
|
|
|
feat(api-client): add Yavsc.Api.Client with Blog + Circle + BlogAcl clients
Creates the high-level HTTP client library the PostIt UI will
consume to manage blog posts, circles, and per-post ACLs.
Clients in this commit:
- BlogApiClient (moved from PostIt/Services; same public surface,
now depends on IYavscApiClient instead of the concrete class).
- CircleApiClient (new): GET/POST/PUT/DELETE /api/circle. Takes
the blogs base URL explicitly in its constructor so it doesn't
need to know about PostIt's Settings type.
- BlogAclApiClient (new): GET/POST/PUT/DELETE /api/blogacl.
Same conventions as CircleApiClient.
DTOs (Yavsc.Api.Client.Dtos):
- CircleDto: id, name, ownerId, public. Stops short of the
navigation properties on the server-side Circle (Owner,
Members), which depend on ApplicationUser and other server
types we don't want to drag into the client.
- CircleAuthorizationDto: circleId, blogPostId, comment. Same
reason: the server entity has Target and Allowed navigation
properties the client never needs.
The clients now require the caller to pass the blogs base URL
explicitly in the constructor (previously the BlogApiClient
sniffed it off YavscApiClient.Settings.BlogsApiUrl, but that
field is PostIt-specific). The one production call site
(App.axaml.cs) and four test call sites are updated to pass
the URL.
Build + 51/51 tests green. The IYavscApiClient abstraction was
landed in the previous commit so this one could be a pure
addition + relocation.
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));
|
feat(api-client): add Yavsc.Api.Client with Blog + Circle + BlogAcl clients
Creates the high-level HTTP client library the PostIt UI will
consume to manage blog posts, circles, and per-post ACLs.
Clients in this commit:
- BlogApiClient (moved from PostIt/Services; same public surface,
now depends on IYavscApiClient instead of the concrete class).
- CircleApiClient (new): GET/POST/PUT/DELETE /api/circle. Takes
the blogs base URL explicitly in its constructor so it doesn't
need to know about PostIt's Settings type.
- BlogAclApiClient (new): GET/POST/PUT/DELETE /api/blogacl.
Same conventions as CircleApiClient.
DTOs (Yavsc.Api.Client.Dtos):
- CircleDto: id, name, ownerId, public. Stops short of the
navigation properties on the server-side Circle (Owner,
Members), which depend on ApplicationUser and other server
types we don't want to drag into the client.
- CircleAuthorizationDto: circleId, blogPostId, comment. Same
reason: the server entity has Target and Allowed navigation
properties the client never needs.
The clients now require the caller to pass the blogs base URL
explicitly in the constructor (previously the BlogApiClient
sniffed it off YavscApiClient.Settings.BlogsApiUrl, but that
field is PostIt-specific). The one production call site
(App.axaml.cs) and four test call sites are updated to pass
the URL.
Build + 51/51 tests green. The IYavscApiClient abstraction was
landed in the previous commit so this one could be a pure
addition + relocation.
2026-08-17 23:50:35 +01:00
|
|
|
if (string.IsNullOrEmpty(blogsBaseAddress))
|
|
|
|
|
throw new ArgumentException("Base address is required.", nameof(blogsBaseAddress));
|
2026-07-08 19:03:54 +01:00
|
|
|
|
feat(api-client): add Yavsc.Api.Client with Blog + Circle + BlogAcl clients
Creates the high-level HTTP client library the PostIt UI will
consume to manage blog posts, circles, and per-post ACLs.
Clients in this commit:
- BlogApiClient (moved from PostIt/Services; same public surface,
now depends on IYavscApiClient instead of the concrete class).
- CircleApiClient (new): GET/POST/PUT/DELETE /api/circle. Takes
the blogs base URL explicitly in its constructor so it doesn't
need to know about PostIt's Settings type.
- BlogAclApiClient (new): GET/POST/PUT/DELETE /api/blogacl.
Same conventions as CircleApiClient.
DTOs (Yavsc.Api.Client.Dtos):
- CircleDto: id, name, ownerId, public. Stops short of the
navigation properties on the server-side Circle (Owner,
Members), which depend on ApplicationUser and other server
types we don't want to drag into the client.
- CircleAuthorizationDto: circleId, blogPostId, comment. Same
reason: the server entity has Target and Allowed navigation
properties the client never needs.
The clients now require the caller to pass the blogs base URL
explicitly in the constructor (previously the BlogApiClient
sniffed it off YavscApiClient.Settings.BlogsApiUrl, but that
field is PostIt-specific). The one production call site
(App.axaml.cs) and four test call sites are updated to pass
the URL.
Build + 51/51 tests green. The IYavscApiClient abstraction was
landed in the previous commit so this one could be a pure
addition + relocation.
2026-08-17 23:50:35 +01:00
|
|
|
// e.g. "https://blogs.pschneider.fr/api/v1/" — keep the
|
2026-07-08 19:03:54 +01:00
|
|
|
// trailing slash so relative paths ("posts") resolve correctly.
|
feat(api-client): add Yavsc.Api.Client with Blog + Circle + BlogAcl clients
Creates the high-level HTTP client library the PostIt UI will
consume to manage blog posts, circles, and per-post ACLs.
Clients in this commit:
- BlogApiClient (moved from PostIt/Services; same public surface,
now depends on IYavscApiClient instead of the concrete class).
- CircleApiClient (new): GET/POST/PUT/DELETE /api/circle. Takes
the blogs base URL explicitly in its constructor so it doesn't
need to know about PostIt's Settings type.
- BlogAclApiClient (new): GET/POST/PUT/DELETE /api/blogacl.
Same conventions as CircleApiClient.
DTOs (Yavsc.Api.Client.Dtos):
- CircleDto: id, name, ownerId, public. Stops short of the
navigation properties on the server-side Circle (Owner,
Members), which depend on ApplicationUser and other server
types we don't want to drag into the client.
- CircleAuthorizationDto: circleId, blogPostId, comment. Same
reason: the server entity has Target and Allowed navigation
properties the client never needs.
The clients now require the caller to pass the blogs base URL
explicitly in the constructor (previously the BlogApiClient
sniffed it off YavscApiClient.Settings.BlogsApiUrl, but that
field is PostIt-specific). The one production call site
(App.axaml.cs) and four test call sites are updated to pass
the URL.
Build + 51/51 tests green. The IYavscApiClient abstraction was
landed in the previous commit so this one could be a pure
addition + relocation.
2026-08-17 23:50:35 +01:00
|
|
|
_baseAddress = new Uri(blogsBaseAddress);
|
|
|
|
|
api.Http.BaseAddress = _baseAddress;
|
2026-07-08 19:03:54 +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
|
|
|
_pathPrefix = pathPrefix?.TrimStart('/') ?? DefaultPathPrefix;
|
2026-06-10 00:23:17 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-18 00:20:01 +01:00
|
|
|
public Task<List<BlogPostDto>> GetPostsAsync(int start = 0, int take = 25, CancellationToken ct = default)
|
|
|
|
|
=> _api.CallAsync<List<BlogPostDto>>(
|
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
|
|
|
HttpMethod.Get,
|
|
|
|
|
$"{_pathPrefix}?start={start}&take={take}",
|
|
|
|
|
ct: ct);
|
2026-06-10 00:23:17 +01:00
|
|
|
|
2026-08-18 00:20:01 +01:00
|
|
|
public Task<BlogPostDto?> GetPostAsync(long id, CancellationToken ct = default)
|
|
|
|
|
=> _api.CallAsync<BlogPostDto?>(HttpMethod.Get, $"{_pathPrefix}/{id}", ct: ct);
|
2026-06-10 00:23:17 +01:00
|
|
|
|
2026-08-18 00:20:01 +01:00
|
|
|
public Task<BlogPostDto?> CreatePostAsync(BlogPostDto post, CancellationToken ct = default)
|
|
|
|
|
=> _api.CallAsync<BlogPostDto?>(HttpMethod.Post, _pathPrefix, body: post, ct: ct);
|
2026-06-10 00:23:17 +01:00
|
|
|
|
2026-08-18 00:20:01 +01:00
|
|
|
public Task UpdatePostAsync(long id, BlogPostDto post, CancellationToken ct = default)
|
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.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
|
|
|
}
|