Compare commits
2 commits
f2ae01729a
...
20a6f22ec3
| Author | SHA1 | Date | |
|---|---|---|---|
| 20a6f22ec3 | |||
| 0d2d4160af |
5 changed files with 32 additions and 18 deletions
|
|
@ -56,6 +56,7 @@ pas vers un serveur HTTP.
|
||||||
|---------------------------------|-------------------------------------------------------------------|
|
|---------------------------------|-------------------------------------------------------------------|
|
||||||
| `Services/OidcLoginPhase` | Enum des étapes du flow : `Idle / Discovering / OpeningBrowser / AwaitingCallback / ExchangingCode / Success / Error` |
|
| `Services/OidcLoginPhase` | Enum des étapes du flow : `Idle / Discovering / OpeningBrowser / AwaitingCallback / ExchangingCode / Success / Error` |
|
||||||
| `Services/YavscApiClient` | Client HTTP de l'API Yavsc. Porte `LoginInteractiveAsync(IProgress<OidcLoginPhase>)` et `TrySilentLoginAsync`. Refresh silencieux sur 401 et sur access-token bientôt expiré. |
|
| `Services/YavscApiClient` | Client HTTP de l'API Yavsc. Porte `LoginInteractiveAsync(IProgress<OidcLoginPhase>)` et `TrySilentLoginAsync`. Refresh silencieux sur 401 et sur access-token bientôt expiré. |
|
||||||
|
| `Services/BlogApiClient` | Mapper DTO↔path pour la sous-API blog. **Note** : `pathPrefix` est *relatif* à `/api/v1/` (que porte déjà `BaseAddress`) — ex. `"blog"` pour matcher `[Route(APIPrefix + "/blog")]`. Ne pas ré-inclure `api/`. |
|
||||||
| `Services/SingleInstance` | Named-pipe helper. `TryHandOffAsync` côté 2ᵉ instance, `StartServerAsync` côté instance vivante. |
|
| `Services/SingleInstance` | Named-pipe helper. `TryHandOffAsync` côté 2ᵉ instance, `StartServerAsync` côté instance vivante. |
|
||||||
| `Services/CustomSchemeBrowser` | `IBrowser` OidcClient qui ouvre le système + attend le pipe. |
|
| `Services/CustomSchemeBrowser` | `IBrowser` OidcClient qui ouvre le système + attend le pipe. |
|
||||||
| `Services/SchemeUrlDetector` | Détection pure, testable, du `postit://callback` dans argv. |
|
| `Services/SchemeUrlDetector` | Détection pure, testable, du `postit://callback` dans argv. |
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,16 @@ namespace PostIt.Services;
|
||||||
/// <see cref="YavscApiClient"/>. This class is a thin DTO↔path
|
/// <see cref="YavscApiClient"/>. This class is a thin DTO↔path
|
||||||
/// mapper, nothing more.
|
/// mapper, nothing more.
|
||||||
///
|
///
|
||||||
|
/// <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>
|
||||||
|
///
|
||||||
/// The class is intentionally non-IDisposable: it does not own the
|
/// The class is intentionally non-IDisposable: it does not own the
|
||||||
/// <see cref="YavscApiClient"/> it depends on. Lifetimes are managed
|
/// <see cref="YavscApiClient"/> it depends on. Lifetimes are managed
|
||||||
/// by the consumer (typically a singleton service registered with
|
/// by the consumer (typically a singleton service registered with
|
||||||
|
|
@ -22,7 +32,7 @@ namespace PostIt.Services;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class BlogApiClient
|
public sealed class BlogApiClient
|
||||||
{
|
{
|
||||||
private const string DefaultPathPrefix = "api/blog";
|
private const string DefaultPathPrefix = "blog";
|
||||||
|
|
||||||
private readonly YavscApiClient _api;
|
private readonly YavscApiClient _api;
|
||||||
private readonly string _pathPrefix;
|
private readonly string _pathPrefix;
|
||||||
|
|
|
||||||
|
|
@ -128,9 +128,28 @@ public partial class MainPageViewModel : ViewModelBase
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
internal async Task Save()
|
internal async Task Save()
|
||||||
{
|
{
|
||||||
|
// No selection means "create a new post from the editor".
|
||||||
|
// The server is the source of truth, so we POST without an id
|
||||||
|
// and let BlogApiController assign one. The local view-model
|
||||||
|
// is then rebound to the server-issued record.
|
||||||
if (SelectedPost is null)
|
if (SelectedPost is null)
|
||||||
{
|
{
|
||||||
StatusMessage = "A post must be selected before saving.";
|
var draft = new BlogPost
|
||||||
|
{
|
||||||
|
Title = string.Empty,
|
||||||
|
Article = string.Empty,
|
||||||
|
DateCreated = DateTime.UtcNow,
|
||||||
|
DateModified = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
await ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
var created = await BlogClient.CreatePostAsync(draft);
|
||||||
|
if (created is not null)
|
||||||
|
{
|
||||||
|
SelectedPost = created;
|
||||||
|
StatusMessage = $"Created post {created.Id}.";
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,19 +195,6 @@ public partial class MainPageViewModel : ViewModelBase
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
internal void New()
|
|
||||||
{
|
|
||||||
SelectedPost = new BlogPost
|
|
||||||
{
|
|
||||||
Title = string.Empty,
|
|
||||||
Article = string.Empty,
|
|
||||||
DateCreated = DateTime.UtcNow,
|
|
||||||
DateModified = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
StatusMessage = "New blog post ready.";
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
internal void OpenSettings()
|
internal void OpenSettings()
|
||||||
{
|
{
|
||||||
|
|
@ -253,7 +259,6 @@ public partial class MainPageViewModel : ViewModelBase
|
||||||
LoadPostsCommand.NotifyCanExecuteChanged();
|
LoadPostsCommand.NotifyCanExecuteChanged();
|
||||||
SaveCommand.NotifyCanExecuteChanged();
|
SaveCommand.NotifyCanExecuteChanged();
|
||||||
DeleteCommand.NotifyCanExecuteChanged();
|
DeleteCommand.NotifyCanExecuteChanged();
|
||||||
NewCommand.NotifyCanExecuteChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanSave() => SelectedPost is not null && !IsBusy;
|
private bool CanSave() => SelectedPost is not null && !IsBusy;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<Button Command="{Binding LoadPosts}" Content="Load posts" />
|
<Button Command="{Binding LoadPosts}" Content="Load posts" />
|
||||||
<Button Command="{Binding Search}" Content="Filter" />
|
<Button Command="{Binding Search}" Content="Filter" />
|
||||||
<Button Command="{Binding New}" Content="New post" />
|
|
||||||
<Button Command="{Binding Save}" Content="Save" />
|
<Button Command="{Binding Save}" Content="Save" />
|
||||||
<Button Command="{Binding Delete}" Content="Delete" />
|
<Button Command="{Binding Delete}" Content="Delete" />
|
||||||
<!--
|
<!--
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ namespace Yavsc.Blogs.Controllers
|
||||||
[Authorize("BlogScope")]
|
[Authorize("BlogScope")]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route(APIPrefix + "/blog")]
|
[Route(APIPrefix + "/blog")]
|
||||||
|
|
||||||
public class BlogApiController : Controller
|
public class BlogApiController : Controller
|
||||||
{
|
{
|
||||||
private readonly BlogSpotService blogSpotService;
|
private readonly BlogSpotService blogSpotService;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue