using System; using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Yavsc.Blogspot; namespace Yavsc.Api.Client; /// /// High-level client for the Blog subsystem of the Yavsc API /// (deployed at https://blogs.pschneider.fr). All transport /// concerns — base URL, JSON serialisation, Bearer auth, silent /// refresh on 401, request body shaping — are delegated to /// , which lives in the consuming /// application (PostIt). This class is a thin DTO↔path mapper, /// nothing more. /// /// URL convention. 's /// BaseAddress already terminates with /api/v1/ /// (see Settings.ApiUrl). The path prefix below is /// therefore relative to that version segment: a prefix of /// "blog" resolves to …/api/v1/blog, which matches /// the [Route(APIPrefix + "/blog")] attribute on /// Yavsc.Blogs.Controllers.BlogApiController. Do not /// re-include the api/ segment here — that produced 404s /// in the past (see commit "PostIt: fix blog API double-prefix"). /// /// The class is intentionally non-IDisposable: it does not own the /// it depends on. Lifetimes are managed /// by the consumer (typically a singleton service registered with /// the application). /// public sealed class BlogApiClient { private const string DefaultPathPrefix = "blog"; private readonly IYavscApiClient _api; private readonly Uri _baseAddress; private readonly string _pathPrefix; public BlogApiClient(IYavscApiClient api, string blogsBaseAddress, string pathPrefix = DefaultPathPrefix) { _api = api ?? throw new ArgumentNullException(nameof(api)); if (string.IsNullOrEmpty(blogsBaseAddress)) throw new ArgumentException("Base address is required.", nameof(blogsBaseAddress)); // e.g. "https://blogs.pschneider.fr/api/v1/" — keep the // trailing slash so relative paths ("posts") resolve correctly. _baseAddress = new Uri(blogsBaseAddress); api.Http.BaseAddress = _baseAddress; _pathPrefix = pathPrefix?.TrimStart('/') ?? DefaultPathPrefix; } public Task> GetPostsAsync(int start = 0, int take = 25, CancellationToken ct = default) => _api.CallAsync>( HttpMethod.Get, $"{_pathPrefix}?start={start}&take={take}", ct: ct); public Task GetPostAsync(long id, CancellationToken ct = default) => _api.CallAsync(HttpMethod.Get, $"{_pathPrefix}/{id}", ct: ct); public Task CreatePostAsync(BlogPost post, CancellationToken ct = default) => _api.CallAsync(HttpMethod.Post, _pathPrefix, body: post, ct: ct); public Task UpdatePostAsync(long id, BlogPost post, CancellationToken ct = default) => _api.CallAsync(HttpMethod.Put, $"{_pathPrefix}/{id}", body: post, ct: ct); public Task DeletePostAsync(long id, CancellationToken ct = default) => _api.CallAsync(HttpMethod.Delete, $"{_pathPrefix}/{id}", ct: ct); }