diff --git a/src/PostIt/PostIt.Tests/BearerScopeTests.cs b/src/PostIt/PostIt.Tests/BearerScopeTests.cs index 984483fc..c6bf7d56 100644 --- a/src/PostIt/PostIt.Tests/BearerScopeTests.cs +++ b/src/PostIt/PostIt.Tests/BearerScopeTests.cs @@ -67,7 +67,7 @@ public class BearerScopeTests Scopes = userScopes, RedirectUri = "postit://callback", }, - ApiUrl = "https://example.invalid/api/v1/", + BusinessApiUrl = "https://example.invalid/api/v1/", }; var tokensPath = Path.Combine( @@ -266,7 +266,7 @@ public class BearerScopeTests // private HttpClient is independent, so we resolve the // absolute URI ourselves from Settings.BusinessApiUrl — // the same URL BlogApiClient would have set as BaseAddress. - var absolute = new Uri(new Uri(Settings.ApiUrl), path); + var absolute = new Uri(new Uri(Settings.BusinessApiUrl), path); using var req = new HttpRequestMessage(method, absolute); req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken); diff --git a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs index e7175203..dd277629 100644 --- a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs +++ b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs @@ -95,7 +95,7 @@ public class PostAclDialogTests HttpMethod method, string path, object? body = null, CancellationToken ct = default) { - var absolute = new Uri(new Uri(Settings.ApiUrl), path); + var absolute = new Uri(new Uri(Settings.BusinessApiUrl), path); using var req = new HttpRequestMessage(method, absolute); using var resp = _http.SendAsync(req, ct).GetAwaiter().GetResult(); resp.EnsureSuccessStatusCode(); @@ -123,8 +123,8 @@ public class PostAclDialogTests var handler = new CountingHttpHandler(); var settings = new Settings(); var api = new TestableYavscApiClient(settings, new TokenStore(System.IO.Path.GetTempFileName()), handler); - var aclClient = new BlogAclApiClient(api, settings.ApiUrl); - var circleClient = new CircleApiClient(api, settings.ApiUrl); + var aclClient = new BlogAclApiClient(api, settings.BusinessApiUrl); + var circleClient = new CircleApiClient(api, settings.BusinessApiUrl); var services = new ServiceCollection(); services.AddSingleton(settings); diff --git a/src/PostIt/PostIt.Tests/SettingsLoadTests.cs b/src/PostIt/PostIt.Tests/SettingsLoadTests.cs index 3f8dc02b..3f350240 100644 --- a/src/PostIt/PostIt.Tests/SettingsLoadTests.cs +++ b/src/PostIt/PostIt.Tests/SettingsLoadTests.cs @@ -89,7 +89,7 @@ public class SettingsLoadTests settings.Authentication.RedirectUri = global::AuthenticationSettings.DesktopRedirectUri; - settings.ApiUrl = flip + settings.BusinessApiUrl = flip ? "https://a.example.test/api/v1/" : "https://b.example.test/api/v1/"; diff --git a/src/PostIt/PostIt.Tests/YavscApiClientTests.cs b/src/PostIt/PostIt.Tests/YavscApiClientTests.cs index b074de66..21c0dd00 100644 --- a/src/PostIt/PostIt.Tests/YavscApiClientTests.cs +++ b/src/PostIt/PostIt.Tests/YavscApiClientTests.cs @@ -58,7 +58,7 @@ public class YavscApiClientTests // calls CallAsync("posts", ...) directly (bypassing // BlogApiClient, which is the only thing that would set // it in production). Mirror prod here. - reloaded.Http.BaseAddress = new Uri(settings.ApiUrl); + reloaded.Http.BaseAddress = new Uri(settings.BusinessApiUrl); var posts = await reloaded.CallAsync>( HttpMethod.Get, "posts", TestContext.Current.CancellationToken); @@ -118,7 +118,7 @@ public class YavscApiClientTests RedirectUri = "postit://callback", Scopes = new[] { "openid" }, }, - ApiUrl = "https://127.0.0.1:5003/api/v1", + BusinessApiUrl = "https://127.0.0.1:5003/api/v1", }; var client = new YavscApiClient(settings, new TokenStore(Path.Combine( Path.GetTempPath(), $"postit-tests-noop-{Guid.NewGuid():N}.json"))); @@ -162,7 +162,7 @@ public class YavscApiClientTests RedirectUri = authority.LoopbackRedirectUri, Scopes = new[] { "openid", "profile", "blog" } }, - ApiUrl = apiBaseUrl + BusinessApiUrl = apiBaseUrl }; private static async Task LoginAndPersistAsync( @@ -175,7 +175,7 @@ public class YavscApiClientTests // directly (bypassing BlogApiClient) rely on the same // BaseAddress the production chain sets in BlogApiClient's // ctor. Mirror that here so "posts" resolves to the stub. - client.Http.BaseAddress = new Uri(settings.ApiUrl); + client.Http.BaseAddress = new Uri(settings.BusinessApiUrl); // Force the API client to use the test browser by routing the // LoginInteractiveAsync call through a small wrapper. diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs index 1b71e873..ff532b2b 100644 --- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs @@ -24,8 +24,8 @@ public static class ServiceCollectionHelpers var circleClient = new CircleApiClient(api, settings.BlogsApiUrl); var blogAclClient = new BlogAclApiClient(api, settings.BlogsApiUrl); var userSearchClient = new UserSearchClient(api, settings.BlogsApiUrl); - var activityClient = new ActivityApiClient(api, settings.ApiUrl); - var billingClient = new BillingApiClient(api, settings.ApiUrl); + var activityClient = new ActivityApiClient(api, settings.BusinessApiUrl); + var billingClient = new BillingApiClient(api, settings.BusinessApiUrl); var userDirectory = new UserDirectory(userSearchClient); // Vues diff --git a/src/PostIt/PostIt/Services/YavscApiClient.cs b/src/PostIt/PostIt/Services/YavscApiClient.cs index 4d748f87..726b3f4f 100644 --- a/src/PostIt/PostIt/Services/YavscApiClient.cs +++ b/src/PostIt/PostIt/Services/YavscApiClient.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -352,77 +351,6 @@ public class YavscApiClient : IYavscApiClient, IAsyncDisposable _store.Save(_tokens); } - /// - /// Upload a user avatar to the Yavsc API. The server expects a - /// single multipart file named file and validates the image - /// content type before persisting it. - /// - public async Task SetAvatarAsync( - Stream imageStream, - string fileName, - string? contentType = null, - CancellationToken ct = default) - { - if (imageStream is null) - throw new ArgumentNullException(nameof(imageStream)); - if (string.IsNullOrWhiteSpace(fileName)) - throw new ArgumentException("A file name is required.", nameof(fileName)); - - var endpoint = new Uri(new Uri(Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), "account/set-avatar"); - - await EnsureFreshTokenAsync(ct).ConfigureAwait(false); - - var attemptUpload = async () => - { - if (imageStream.CanSeek) - imageStream.Position = 0; - - using var content = new MultipartFormDataContent(); - using var fileContent = new StreamContent(imageStream); - fileContent.Headers.ContentType = new MediaTypeHeaderValue( - string.IsNullOrWhiteSpace(contentType) ? "application/octet-stream" : contentType); - content.Add(fileContent, "file", fileName); - - using var request = new HttpRequestMessage(HttpMethod.Post, endpoint) - { - Content = content, - }; - - return await Http.SendAsync(request, ct).ConfigureAwait(false); - }; - - var response = await attemptUpload().ConfigureAwait(false); - if (response.StatusCode == HttpStatusCode.Unauthorized) - { - response.Dispose(); - await ForceRefreshAsync(ct).ConfigureAwait(false); - response = await attemptUpload().ConfigureAwait(false); - } - - await EnsureSuccessOrThrowAsync(response, ct).ConfigureAwait(false); - - var payload = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(payload)) - return "Avatar mis à jour."; - - try - { - using var json = JsonDocument.Parse(payload); - if (json.RootElement.TryGetProperty("message", out var msgEl)) - { - var message = msgEl.GetString(); - if (!string.IsNullOrWhiteSpace(message)) - return message; - } - } - catch (JsonException) - { - // Keep a user-friendly fallback when the API payload is not JSON. - } - - return "Avatar mis à jour."; - } - public async Task LogoutAsync() { _store.Clear(); diff --git a/src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs b/src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs index d49fde80..03dab60e 100644 --- a/src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using Yavsc.Abstract.Workflow; using Yavsc.Api.Client; -using Yavsc.Models.Billing; using Yavsc.Models.Haircut; namespace PostIt.ViewModels.Commands; @@ -22,6 +21,7 @@ public partial class BrushViewModel : RdvViewModel [ObservableProperty] public partial HairPrestationDto? SelectedPrestation { get; set; } + public BrushViewModel(ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form, BillingApiClient billingClient) : base(activity, performer, form, billingClient) { diff --git a/src/PostIt/PostIt/ViewModels/Settings/Settings.cs b/src/PostIt/PostIt/ViewModels/Settings/Settings.cs index e63f0b32..0c7a040b 100644 --- a/src/PostIt/PostIt/ViewModels/Settings/Settings.cs +++ b/src/PostIt/PostIt/ViewModels/Settings/Settings.cs @@ -26,7 +26,7 @@ public partial class Settings : ViewModelBase public partial string BlogsApiUrl { get; set; } = "https://blogs.pschneider.fr/api/v1/"; [ObservableProperty] - public partial string ApiUrl { get; set; } = "https://api.pschneider.fr/api/v1/"; + public partial string BusinessApiUrl { get; set; } = "https://api.pschneider.fr/api/v1/"; [ObservableProperty] public partial string SearchText { get; set; } = string.Empty; @@ -45,7 +45,7 @@ public partial class Settings : ViewModelBase partial void OnDarkModeChanged(bool value) => MarkDirty(); partial void OnBlogsApiUrlChanged(string value) => MarkDirty(); - partial void OnApiUrlChanged(string value) => MarkDirty(); + partial void OnBusinessApiUrlChanged(string value) => MarkDirty(); partial void OnSearchTextChanged(string value) => MarkDirty(); /// @@ -306,9 +306,9 @@ public partial class Settings : ViewModelBase this.BlogsApiUrl = !string.IsNullOrWhiteSpace(settings.BlogsApiUrl) ? settings.BlogsApiUrl : legacyApiUrl ?? this.BlogsApiUrl; - this.ApiUrl = !string.IsNullOrWhiteSpace(settings.ApiUrl) - ? settings.ApiUrl - : this.ApiUrl; + this.BusinessApiUrl = !string.IsNullOrWhiteSpace(settings.BusinessApiUrl) + ? settings.BusinessApiUrl + : this.BusinessApiUrl; this.SearchText = settings.SearchText ?? string.Empty; if (!(settings.Authentication is null)) { @@ -384,7 +384,7 @@ public partial class Settings : ViewModelBase }; this.DarkMode = false; this.BlogsApiUrl = "https://blogs.pschneider.fr/api/v1/"; - this.ApiUrl = "https://api.pschneider.fr/api/v1/"; + this.BusinessApiUrl = "https://api.pschneider.fr/api/v1/"; this.SearchText = string.Empty; } diff --git a/src/PostIt/PostIt/Views/Commands/BrushPage.axaml b/src/PostIt/PostIt/Views/Commands/BrushPage.axaml index 9c33cecb..75397abd 100644 --- a/src/PostIt/PostIt/Views/Commands/BrushPage.axaml +++ b/src/PostIt/PostIt/Views/Commands/BrushPage.axaml @@ -33,11 +33,13 @@ + Margin="0,0,12,8" + IsVisible="{Binding ShowsReason}" /> + Margin="0,0,0,8" + IsVisible="{Binding ShowsReason}" /> @@ -58,12 +60,14 @@ + Margin="0,0,12,8" + IsVisible="{Binding ShowsSinglePrestation}" /> + Margin="0,0,0,8" + IsVisible="{Binding ShowsSinglePrestation}"> @@ -74,14 +78,39 @@ + + + + + + + + + + + + + + + Margin="0,0,12,8" + IsVisible="{Binding ShowsAdditionalInfo}" /> + Margin="0,0,0,8" + IsVisible="{Binding ShowsAdditionalInfo}" /> + Margin="0,0,12,8" + IsVisible="{Binding ShowsReason}" /> + Margin="0,0,0,8" + IsVisible="{Binding ShowsReason}" /> @@ -55,11 +57,13 @@ + Margin="0,0,12,8" + IsVisible="{Binding ShowsAdditionalInfo}" /> + Margin="0,0,0,8" + IsVisible="{Binding ShowsAdditionalInfo}" /> - + + Text="{Binding BusinessApiUrl, Mode=TwoWay}"/> - - - - - - -