From 50de5f412f9b3d1d80fe4f5b32d3402239d3f40b Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 23 Aug 2026 23:19:48 +0100 Subject: [PATCH 01/95] chore(release): bump version via gitversion for 1.0.8-rc3 --- src/PostIt.Tests/PostIt.Tests.csproj | 2 +- src/PostIt/PostIt.Android/PostIt.Android.csproj | 4 ++-- src/PostIt/PostIt.Browser/PostIt.Browser.csproj | 2 +- src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj | 2 +- src/PostIt/PostIt/PostIt.csproj | 2 +- src/Yavsc.Abstract/Yavsc.Abstract.csproj | 2 +- src/Yavsc.Api.Client/Yavsc.Api.Client.csproj | 2 +- src/Yavsc.Api/Yavsc.Api.csproj | 2 +- src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj | 2 +- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 2 +- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 2 +- src/Yavsc.Org/Yavsc.Org.csproj | 2 +- src/Yavsc.Server/Yavsc.Server.csproj | 2 +- src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj | 2 +- src/cli/cli.csproj | 4 ++-- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/PostIt.Tests/PostIt.Tests.csproj b/src/PostIt.Tests/PostIt.Tests.csproj index b12c536e..b41c8137 100644 --- a/src/PostIt.Tests/PostIt.Tests.csproj +++ b/src/PostIt.Tests/PostIt.Tests.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index b34b88d4..40651399 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -14,7 +14,7 @@ android-arm;android-arm64;android-x86;android-x64 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -33,4 +33,4 @@ - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index 8643fcc6..b3376ce7 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -6,7 +6,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index 5043da6e..70795584 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -7,7 +7,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index 163a6a77..0bef1f64 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -6,7 +6,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 7a4f83ae..964ad179 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -11,7 +11,7 @@ latest 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index ade7ca17..5be01943 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -17,7 +17,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index 5672ab7a..e714a0eb 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index fc7883ce..8e4574aa 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 5c175cb5..aa54a758 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 88842cb4..672e8f5c 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -11,7 +11,7 @@ $(MSBuildProjectDirectory)\test.runsettings 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index ccdb7f24..ef710ea9 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -9,7 +9,7 @@ https://github.com/pazof/yavsc 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index 4eaf6a83..3eea8b3f 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index 52effc72..a059b4a3 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -14,7 +14,7 @@ --> 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index 010c4eaf..237ccdd8 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -25,4 +25,4 @@ - + \ No newline at end of file From 01bc4782a01684528da41632651b2d385205d0ee Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 23 Aug 2026 23:30:37 +0100 Subject: [PATCH 02/95] changes --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a446a0..4147fc40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,21 @@ pour la production des paquets `.deb`. ## [1.0.8-rc1] - unstable +### Added + +* a code cleanup +* a failling a disabled test ala Xamarin.UITest : the Android app stil doesn't starts + +### Changed + +moslty nothing + +### Fixed + +nothing + +## [1.0.8-rc1] - unstable + ### Added - `BlogAclApiTests.PostCircleAuthorization_returns_201_when_payload_mirrors_PostIt_shape_against_existing_circle_named_test` : test de non-régression qui épingle la forme exacte du payload From 404bba56ab0d1399094b71e4316143d576422b99 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 00:03:14 +0100 Subject: [PATCH 03/95] restores the legacy behavior --- src/PostIt/PostIt/App.axaml.cs | 6 ++--- .../Helpers/ServiceCollectionHelpers.cs | 2 +- src/PostIt/PostIt/ViewLocator.cs | 22 ++++++++----------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index 10a0c8a5..b23a03f1 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -65,7 +65,10 @@ public partial class App : Application singleViewPlatform.MainView = CreateMainWindow(); ApplyDarkMode(settings); } + var mainVm = ServiceProvider!.GetRequiredService(); + base.OnFrameworkInitializationCompleted(); + this.PushPageAsync(mainVm).Wait(); } @@ -86,9 +89,6 @@ public partial class App : Application PushMainPageAsync().Wait(); }; - var homeVm = ServiceProvider!.GetRequiredService(); - - this.PushPageAsync(homeVm).Wait(); Window.SessionBanner.DataContext = sessionStatus; return Window; } diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs index 9e0382de..fae24fa6 100644 --- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs @@ -74,4 +74,4 @@ public static class ServiceCollectionHelpers return services.BuildServiceProvider(); } -} \ No newline at end of file +} diff --git a/src/PostIt/PostIt/ViewLocator.cs b/src/PostIt/PostIt/ViewLocator.cs index 03ab98a1..3543a9c9 100644 --- a/src/PostIt/PostIt/ViewLocator.cs +++ b/src/PostIt/PostIt/ViewLocator.cs @@ -16,12 +16,6 @@ namespace PostIt; Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] public class ViewLocator : IDataTemplate { - private readonly IServiceProvider _services; - - public ViewLocator(IServiceProvider services) - { - _services = services; - } public Control Build(object? data) { @@ -38,15 +32,17 @@ public class ViewLocator : IDataTemplate private Control BuildCore(object? data) { + var app = App.Current as App; + var services = app!.ServiceProvider!; return data switch { - MainViewModel => _services.GetRequiredService(), - Settings => _services.GetRequiredService(), - HomePageViewModel => _services.GetRequiredService(), - SignaturePageViewModel => _services.GetRequiredService(), - AddCircleMemberDialogViewModel => _services.GetRequiredService(), - CirclesPageViewModel => _services.GetRequiredService(), - PostAclDialogViewModel => _services.GetRequiredService(), + MainViewModel => services.GetRequiredService(), + Settings => services.GetRequiredService(), + HomePageViewModel => services.GetRequiredService(), + SignaturePageViewModel => services.GetRequiredService(), + AddCircleMemberDialogViewModel => services.GetRequiredService(), + CirclesPageViewModel => services.GetRequiredService(), + PostAclDialogViewModel => services.GetRequiredService(), null => new TextBlock { Text = "No view for " }, _ => new TextBlock { Text = $"No view for {data.GetType().Name}" } }; From 11aaf3678916438453f82796268ea7e9c7f28c5f Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 00:17:43 +0100 Subject: [PATCH 04/95] updated --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4147fc40..73a4a88f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,20 +16,21 @@ Cette convention est partagée avec le dépôt [`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) pour la production des paquets `.deb`. -## [1.0.8-rc1] - unstable +## [1.0.8-rc3] - unstable ### Added * a code cleanup * a failling a disabled test ala Xamarin.UITest : the Android app stil doesn't starts +- Android app does not crash any more, but still no home page, only the splash screnn ### Changed -moslty nothing +mostly nothing ### Fixed -nothing +mostly nothing ## [1.0.8-rc1] - unstable From 246111dee146d4b92dfc2f4ed4891efcbeb33cb9 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 00:18:07 +0100 Subject: [PATCH 05/95] Simpler is better --- src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs | 2 -- src/PostIt/PostIt.Tests/MainPageButtonsTests.cs | 5 ----- src/PostIt/PostIt.Tests/PostAclDialogTests.cs | 2 -- 3 files changed, 9 deletions(-) diff --git a/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs b/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs index e2426457..91413ca0 100644 --- a/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs +++ b/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs @@ -100,8 +100,6 @@ public class AddCircleMemberDialogTests context.Window = new MainWindow(); context.App = (PostIt.App)Application.Current!; - context.App.DataTemplates.Clear(); - context.App.DataTemplates.Add(new ViewLocator(sp)); context.App.AttachMainWindow(context.Window); context.Window.Show(); diff --git a/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs b/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs index 3e7344a2..cd3684ef 100644 --- a/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs +++ b/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs @@ -114,11 +114,6 @@ public class MainPageButtonsTests var window = new MainWindow(); var page = new MainPage { DataContext = vm }; var app = (PostIt.App)Application.Current!; - if (vm.Services is not null) - { - app.DataTemplates.Clear(); - app.DataTemplates.Add(new ViewLocator(vm.Services)); - } app.AttachMainWindow(window); window.Show(); window.NavRoot.PushAsync(page).GetAwaiter().GetResult(); diff --git a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs index 9bc28888..d13468f0 100644 --- a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs +++ b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs @@ -140,8 +140,6 @@ public class PostAclDialogTests var window = new MainWindow(); var app = (App)Application.Current!; - app.DataTemplates.Clear(); - app.DataTemplates.Add(new ViewLocator(sp)); app.AttachMainWindow(window); window.Show(); From 88e1499e4be5b69c45184258e6b35474be5f2914 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 00:30:29 +0100 Subject: [PATCH 06/95] show the window --- src/PostIt/PostIt/App.axaml.cs | 5 +++-- src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index b23a03f1..1d0933a7 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -47,7 +47,7 @@ public partial class App : Application if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = CreateMainWindow(); + desktop.MainWindow = Window = CreateMainWindow(); ApplyDarkMode(settings); } else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) @@ -57,12 +57,13 @@ public partial class App : Application { Window = CreateMainWindow(); ApplyDarkMode(settings); + Window.Show(); return Window; }; } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) { - singleViewPlatform.MainView = CreateMainWindow(); + singleViewPlatform.MainView = Window = CreateMainWindow(); ApplyDarkMode(settings); } var mainVm = ServiceProvider!.GetRequiredService(); diff --git a/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs b/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs index 7fc81b3a..3cb00301 100644 --- a/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs @@ -10,7 +10,8 @@ public static class ViewModelBaseHelpers { public static async Task PushPageAsync(this App app, ViewModelBase vm) { - if (app.Window is null) + var window = app.Window; + if (window is null) { throw new InvalidOperationException("MainWindow is not initialized yet."); } @@ -39,12 +40,12 @@ public static class ViewModelBaseHelpers page.DataContext = vm; // Avoid stacking the same singleton page twice (e.g. SettingsPage). - var stack = app.Window.NavRoot.NavigationStack; + var stack = window.NavRoot.NavigationStack; if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], page)) { return; } - await app.Window.NavRoot.PushAsync(page); + await window.NavRoot.PushAsync(page); } -} \ No newline at end of file +} From f62a020c82b913517cae5c772dc22e3c94259fe4 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 01:23:15 +0100 Subject: [PATCH 07/95] don't wait from App Nav push ok on loggin succeded --- Makefile | 5 +++-- src/PostIt/PostIt/App.axaml.cs | 2 +- src/PostIt/PostIt/Views/MainPage.axaml | 11 +++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index d6d69196..05aa5df2 100644 --- a/Makefile +++ b/Makefile @@ -163,7 +163,7 @@ POSTIT_RID ?= android-x64 EMU_HEADLESS ?= 0 LOGCAT_LINES ?= 600 LOGCAT_FOLLOW ?= 0 -LOGCAT_BOOT_WAIT ?= 20 +LOGCAT_BOOT_WAIT ?= 30 ANDROID_PACKAGE_NAME = fr.pschneider.PostIt POSTIT_ANDROID_CSPROJ := src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -275,7 +275,8 @@ qemu-logcat-boot: @PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \ if [ -n "$$PID" ]; then \ echo " ✅ (PID $$PID at dump time)"; \ - adb -s $(ADB_SERIAL) logcat -d -v time --pid=$$PID; \ + sleep 10; \ + adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID; \ else \ echo " 👿 (PostIt process not running at dump time — dumping last $(LOGCAT_LINES) lines unfiltered)"; \ adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES); \ diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index 1d0933a7..3b7370f9 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -87,7 +87,7 @@ public partial class App : Application sessionStatus.LoginSucceeded += () => { - PushMainPageAsync().Wait(); + PushMainPageAsync(); }; Window.SessionBanner.DataContext = sessionStatus; diff --git a/src/PostIt/PostIt/Views/MainPage.axaml b/src/PostIt/PostIt/Views/MainPage.axaml index 7f1f1196..d79253f6 100644 --- a/src/PostIt/PostIt/Views/MainPage.axaml +++ b/src/PostIt/PostIt/Views/MainPage.axaml @@ -97,7 +97,14 @@ - + + + From 7fabee1bd14657fa9ac4f1f0be85682f2bce456d Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 01:43:47 +0100 Subject: [PATCH 08/95] restore the TextEditor --- src/PostIt/PostIt/Views/MainPage.axaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PostIt/PostIt/Views/MainPage.axaml b/src/PostIt/PostIt/Views/MainPage.axaml index d79253f6..fd75d403 100644 --- a/src/PostIt/PostIt/Views/MainPage.axaml +++ b/src/PostIt/PostIt/Views/MainPage.axaml @@ -97,14 +97,14 @@ - - - + --> + HorizontalScrollBarVisibility="Auto" /> From ce070453b20069beeb3f13b3a187dbe785665bb7 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 16:25:45 +0100 Subject: [PATCH 09/95] It was too much for qemu --- Makefile | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 05aa5df2..9f1eca7f 100644 --- a/Makefile +++ b/Makefile @@ -124,12 +124,12 @@ release: # Cibles pour installer PostIt.Android en Debug sur l'AVD qemu. # # Usage typique : -# make qemu # lance l'AVD, attend le boot, build l'APK, l'installe -# make qemu-install # (re)build l'APK et l'installe (AVD doit tourner) -# make qemu-build # build l'APK seul (sans install) -# make qemu-run # démarre l'AVD en background -# make qemu-stop # arrête l'émulateur -# make qemu-wait-boot # attend que l'AVD ait fini de booter +# make qemu # lance l'AVD, attend le boot, build l'APK, l'installe +# make android-install # (re)build l'APK et l'installe (AVD doit tourner) +# make android-build # build l'APK seul (sans install) +# make qemu-run # démarre l'AVD en background +# make qemu-stop # arrête l'émulateur +# make qemu-wait-boot # attend que l'AVD ait fini de booter # # Variables surchargeables (make VAR=valeur) : # AVD_NAME default: postit_test_avd @@ -150,7 +150,7 @@ release: # LOGCAT_LINES default: 200 # (nombre de lignes dumpées par `make qemu-logcat`) # LOGCAT_FOLLOW default: 0 -# (1 = stream live via `make qemu-logcat`, +# (1 = stream live via `make logcat`, # sinon dump one-shot des N dernières lignes) # LOGCAT_BOOT_WAIT default: 30 # (secondes d'attente entre le clear du buffer, @@ -165,7 +165,7 @@ LOGCAT_LINES ?= 600 LOGCAT_FOLLOW ?= 0 LOGCAT_BOOT_WAIT ?= 30 -ANDROID_PACKAGE_NAME = fr.pschneider.PostIt +ANDROID_PACKAGE_NAME = fr.pschneider.postit POSTIT_ANDROID_CSPROJ := src/PostIt/PostIt.Android/PostIt.Android.csproj POSTIT_APK_DIR := src/PostIt/PostIt.Android/bin/$(CONFIG)/net10.0-android/$(POSTIT_RID) POSTIT_APK := $(POSTIT_APK_DIR)/$(ANDROID_PACKAGE_NAME)-Signed.apk @@ -197,7 +197,7 @@ qemu-wait-boot: echo " Logs: /tmp/yavsc-emu/$(AVD_NAME).log" >&2; \ exit 1 -qemu-build: +android-build: # EmbedAssembliesIntoApk=true: without this, the Debug APK ships # without the managed assemblies in it (they are pushed at runtime # via `adb push`, "Fast Deployment"). On the qemu emulator, the @@ -223,7 +223,7 @@ qemu-build: fi -qemu-install: qemu-build +android-install: android-build @echo " Installing $(POSTIT_APK) on $(ADB_SERIAL)..." adb -s $(ADB_SERIAL) install -r "$(POSTIT_APK)" -r @@ -240,7 +240,7 @@ qemu-uninstall: # If the app is not running, pidof returns empty and logcat exits # silently with no output; that is the expected behaviour for # "no logs yet". -qemu-logcat: +logcat: @PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \ if [ -z "$$PID" ]; then \ echo " $(ANDROID_PACKAGE_NAME) is not running on $(ADB_SERIAL)."; \ @@ -262,12 +262,16 @@ qemu-logcat: # Override LOGCAT_BOOT_WAIT to extend the post-launch wait # (default 15s; raise to 30+ if the device is slow to boot Avalonia). LOGCAT_BOOT_WAIT ?= 15 -qemu-logcat-boot: + + +android-start: @echo " Clearing logcat buffer..." adb -s $(ADB_SERIAL) logcat -c @echo " Launching $(ANDROID_PACKAGE_NAME)..." adb -s $(ADB_SERIAL) shell am start \ -n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity + +qemu-logcat-boot: android-start @echo " Waiting $(LOGCAT_BOOT_WAIT)s for the app to start rendering..." @sleep $(LOGCAT_BOOT_WAIT) @@ -283,7 +287,7 @@ qemu-logcat-boot: exit 1; \ fi -qemu: qemu-run qemu-wait-boot qemu-install +qemu: qemu-run qemu-wait-boot android-install @echo " ✓ PostIt.Android installed on $(ADB_SERIAL)" -.PHONY: test release qemu qemu-run qemu-stop qemu-wait-boot qemu-build qemu-install qemu-logcat qemu-logcat-boot +.PHONY: test release qemu qemu-run qemu-stop qemu-wait-boot android-build android-install logcat qemu-logcat-boot From a079c56c08116143b508a8a5f792065327aa052a Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 16:26:23 +0100 Subject: [PATCH 10/95] code format --- .vscode/launch.json | 84 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 42be176a..7ea523c9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,10 +1,10 @@ { - // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. - // Pointez pour afficher la description des attributs existants. - // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { "name": "Debug - Android", "type": "mono", "preLaunchTask": "run-debug-android", @@ -19,41 +19,39 @@ "address": "localhost", "port": 10000 }, - { - "name": "API", - "type": "dotnet", - "request": "launch", - "projectPath": "${workspaceFolder}/src/Api/Api.csproj" - }, - { - "name": "Yavsc.Org", - "type": "dotnet", - "request": "launch", - "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj", - }, - { - "name": "Yavsc.Blogs", - "type": "dotnet", - "request": "launch", - "projectPath": "${workspaceFolder}/src/Yavsc.Blogs/Yavsc.Blogs.csproj" - }, - { - "name": "PostIt", - "type": "dotnet", - "request": "launch", - "projectPath": "${workspaceFolder}/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj", - - }, - { - "name": "Test PostIt.Android launch (Xamarin.UITest)", - "type": "coreclr", - "request": "launch", - "program": "${workspaceFolder}/src/PostIt/PostIt.Tests/bin/Debug/net10.0/PostIt.Tests.dll", - "args": [ - ], - "cwd": "${workspaceFolder}/src/PostIt/PostIt.Tests", - "console": "integratedTerminal", - "stopAtEntry": false - } - ] + { + "name": "API", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Api/Api.csproj" + }, + { + "name": "Yavsc.Org", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj", + }, + { + "name": "Yavsc.Blogs", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Yavsc.Blogs/Yavsc.Blogs.csproj" + }, + { + "name": "PostIt", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj", + }, + { + "name": "Test PostIt.Android launch (Xamarin.UITest)", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/src/PostIt/PostIt.Tests/bin/Debug/net10.0/PostIt.Tests.dll", + "args": [], + "cwd": "${workspaceFolder}/src/PostIt/PostIt.Tests", + "console": "integratedTerminal", + "stopAtEntry": false + } + ] } From a450be6f249577d5e0a19808e8a4c814e033a61e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 24 Aug 2026 16:26:42 +0100 Subject: [PATCH 11/95] simpler is better --- .vscode/tasks.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c900fa6a..a80c4741 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,13 +10,11 @@ "env": { "DOTNET_HOST_PATH": "/usr/share/dotnet", "ANDROID_HOME": "/opt/android-sdk", - "JAVA_HOME": "/usr/lib/jvm/java-1.21.0-openjdk-amd64" + "JAVA_HOME": "/usr/lib/jvm/java-1.25.0-openjdk-amd64" } }, "args": [ - "build" - "-t:run", - "-p:TargetFramework=net10.0-android", + "run", "-p:Configuration=Debug", "-p:AndroidAttachDebugger=true", "-p:AndroidSdbHostPort=10000", From beeac586284dfca3a10df7a283d79a047ff39b43 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 14:16:49 +0100 Subject: [PATCH 12/95] test are still ok! --- .vscode/launch.json | 2 +- .vscode/tasks.json | 70 +++++-------------- Directory.Build.props | 2 +- src/PostIt/Directory.Packages.props | 36 +++++----- src/PostIt/Makefile | 7 ++ src/PostIt/PostIt.Android/MainActivity.cs | 23 ++++-- .../PostIt.Android/PostIt.Android.csproj | 12 ++-- .../Resources/values/font_certs.xml | 13 ++++ .../AddCircleMemberDialogTests.cs | 5 +- .../PostIt.Tests/AndroidAppLaunchTests.cs | 2 +- .../PostIt.Tests/MainPageButtonsTests.cs | 11 ++- src/PostIt/PostIt.Tests/PostAclDialogTests.cs | 5 +- .../PostIt.Tests/SessionStatusBannerTests.cs | 17 ++--- src/PostIt/PostIt.Tests/TestAppContext.cs | 2 +- src/PostIt/PostIt.Tests/UnitTest1.cs | 3 +- src/PostIt/PostIt/App.axaml | 1 - src/PostIt/PostIt/App.axaml.cs | 62 ++++++++-------- .../Helpers/ServiceCollectionHelpers.cs | 7 +- .../PostIt/Helpers/ViewModelBaseHelpers.cs | 2 +- src/PostIt/PostIt/PostIt.csproj | 1 - .../PostIt/Services/ContactService.Desktop.cs | 36 ---------- src/PostIt/PostIt/Views/MainPage.axaml | 14 +--- src/PostIt/PostIt/Views/MainView.axaml | 22 ++++++ src/PostIt/PostIt/Views/MainView.axaml.cs | 11 +++ src/PostIt/PostIt/Views/MainWindow.axaml | 9 +-- src/PostIt/PostIt/Views/MainWindow.axaml.cs | 2 +- src/PostIt/PostIt/Views/TextEditorBinding.cs | 51 -------------- 27 files changed, 170 insertions(+), 258 deletions(-) create mode 100644 src/PostIt/Makefile create mode 100644 src/PostIt/PostIt.Android/Resources/values/font_certs.xml delete mode 100644 src/PostIt/PostIt/Services/ContactService.Desktop.cs create mode 100644 src/PostIt/PostIt/Views/MainView.axaml create mode 100644 src/PostIt/PostIt/Views/MainView.axaml.cs delete mode 100644 src/PostIt/PostIt/Views/TextEditorBinding.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index 7ea523c9..36dc17a2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Debug - Android", + "name": "Android", "type": "mono", "preLaunchTask": "run-debug-android", "request": "attach", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a80c4741..9fe9caaf 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -15,12 +15,27 @@ }, "args": [ "run", + "-p:TargetFramework=net10.0-android", "-p:Configuration=Debug", "-p:AndroidAttachDebugger=true", "-p:AndroidSdbHostPort=10000", "-p:AndroidSdbTargetPort=10000" ], - "problemMatcher": "$msCompile" + "problemMatcher": [ + { + "owner": "dotnet", + "fileLocation": ["relative", "${workspaceFolder}"], + "source": "dotnet", + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ] }, { "label": "build", @@ -60,59 +75,6 @@ "kind": "build" }, "isBackground": true - }, - { - "label": "test blogs", - "type": "process", - "problemMatcher": ["$msCompile"], - "command": "dotnet", - "args": ["test"], - "runOptions": { - "instanceLimit": 1 - }, - "options": { - "cwd": "src/Yavsc.Blogs", - "env": { - "DOTNET_CLI_UI_LANGUAGE": "en-US", - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "group": { - "kind": "test" - }, - "isBackground": true, - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": true, - "clear": false - } - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": ["watch", "--project", - "src/Yavsc.Org/Yavsc.Org.csproj" - ], - "problemMatcher": "$msCompile", - "runOptions": { - - } - } ] } diff --git a/Directory.Build.props b/Directory.Build.props index aec8c990..0ce825bd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,6 +11,6 @@ from without conflicting names. --> true - NU1701, NU1901, NU1902 + NU1701, NU1901, NU1902, NU1507 diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props index 69e8a896..0d5fa50c 100644 --- a/src/PostIt/Directory.Packages.props +++ b/src/PostIt/Directory.Packages.props @@ -5,33 +5,35 @@ true + 12.1.1 - - - - - - - - - + + + + + + + + + - + - - - - - - - + + + + + + + + diff --git a/src/PostIt/Makefile b/src/PostIt/Makefile new file mode 100644 index 00000000..72158bfa --- /dev/null +++ b/src/PostIt/Makefile @@ -0,0 +1,7 @@ + +clean: clean-PostIt clean-PostIt.Android clean-PostIt.Desktop + +clean-%: + rm -rf $*/obj $*/bin + +.PHONY: clean diff --git a/src/PostIt/PostIt.Android/MainActivity.cs b/src/PostIt/PostIt.Android/MainActivity.cs index 62c29e10..4f788550 100644 --- a/src/PostIt/PostIt.Android/MainActivity.cs +++ b/src/PostIt/PostIt.Android/MainActivity.cs @@ -1,8 +1,13 @@ -using Android.App; +using Android; +using Android.App; using Android.Content; using Android.Content.PM; +using AndroidX.Core.Provider; +using AndroidX.Emoji2.Text; using Avalonia; using Avalonia.Android; +using AndroidX.Core.Provider; +using AndroidX.Emoji2.Text; namespace PostIt.Android; @@ -15,18 +20,22 @@ namespace PostIt.Android; ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)] public class MainActivity : AvaloniaMainActivity { - /// - /// Strongly-typed handle to the current MainActivity instance, set in - /// and consumed by platform services such as - /// which need to launch - /// Chrome Custom Tabs. + /// + /// The current MainActivity instance. /// public static MainActivity? Current { get; private set; } protected override void OnCreate(global::Android.OS.Bundle? savedInstanceState) { - base.OnCreate(savedInstanceState); + FontRequest fontRequest = new FontRequest( + "com.google.android.gms.fonts", + "com.google.android.gms", + "Noto Color Emoji Compat", + Yavsc.Resource.Array.com_google_android_gms_fonts_certs); //com_google_android_gms_fonts_certs + EmojiCompat.Config config = new FontRequestEmojiCompatConfig(this, fontRequest); + EmojiCompat.Init(config); PlatformBootstrap.EnsureInitialized(); + base.OnCreate(savedInstanceState); Current = this; } /// diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index a4af1eb2..62b494f8 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -4,13 +4,11 @@ net10.0-android 23 enable - fr.pschneider.PostIt + fr.pschneider.postit 1 1.0 apk - false - SdkOnly - partial + false @@ -23,11 +21,11 @@ - - - + + + diff --git a/src/PostIt/PostIt.Android/Resources/values/font_certs.xml b/src/PostIt/PostIt.Android/Resources/values/font_certs.xml new file mode 100644 index 00000000..f4adce1b --- /dev/null +++ b/src/PostIt/PostIt.Android/Resources/values/font_certs.xml @@ -0,0 +1,13 @@ + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + MIIEqDCCA5CgAwIBAgIJAN5gc16AJfAsMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQDEwdBbmRyb2lkMSEwHwYJKoZIhvcNAQkBFhJhbmRyb2lkQGFuZHJvaWQuY29tMCAXDTA4MDQxNTIyNDA0M1YYDzQyMDgxMzA0MjI0MDQzWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDURvdW50YWluIFZpZXcxEDAOBgNVBAoTB0dvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEhMB8GCSqGSIb3DQEJARYSYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALBi1vF0K1vOEHG7AxneTjOHUka46MIidBqvFcO164A49iU2DkYPhUaM4H8JCdzh6N1GzM6h9o6E2V6z8+gEtdI6nqqs0EGA0G0H701bFjLp9+K/1DkMIFeD4P8J7X1/M8t4+X09X/7bQyV3w0v7q+Qh38sY8W/7K29B3f2O2sLw+uX9U8a8Tf4Xv8A== + + + MIIEQzCCAyugAwIBAgIJAMLgh0ZgXpYOMA0GCSqGSIb3DQEBBQUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKKvSkUIXm+t9M8rXj2V + + diff --git a/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs b/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs index 91413ca0..8bec1dc6 100644 --- a/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs +++ b/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs @@ -73,7 +73,7 @@ public class AddCircleMemberDialogTests return context; } /// - /// Mount a real , build a minimal + /// Mount a real , build a minimal /// DI graph, push then the /// on top of it. /// Returns the stack size so the test can pin the delta. @@ -98,10 +98,9 @@ public class AddCircleMemberDialogTests services.AddTransient(); var sp = services.BuildServiceProvider(); - context.Window = new MainWindow(); + context.Window = new MainView(); context.App = (PostIt.App)Application.Current!; context.App.AttachMainWindow(context.Window); - context.Window.Show(); context.page = sp.GetRequiredService(); context.Window.NavRoot.PushAsync(context.page).GetAwaiter().GetResult(); diff --git a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs index ac4756eb..23b423e1 100644 --- a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs +++ b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs @@ -23,7 +23,7 @@ public class AndroidAppLaunchTests _output = output; } - // FIXME [Fact] + [Fact] public void PostIt_starts_and_draws_a_first_frame_on_the_emulator() { if (!IsPackageInstalledOnAnyDevice()) diff --git a/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs b/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs index cd3684ef..d1d00532 100644 --- a/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs +++ b/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs @@ -99,7 +99,7 @@ public class MainPageButtonsTests } /// - /// Mount a real (as + /// Mount a real (as /// SessionStatusBannerTests does), push a /// with the given VM onto /// NavRoot. PushAsync is awaited (via @@ -109,13 +109,12 @@ public class MainPageButtonsTests /// realised and KeyPressQwerty has a real /// to dispatch against. /// - private static (MainWindow window, MainPage page) MountMainPage(MainViewModel vm) + private static (MainView window, MainPage page) MountMainPage(MainViewModel vm) { - var window = new MainWindow(); + var window = new MainView(); var page = new MainPage { DataContext = vm }; var app = (PostIt.App)Application.Current!; app.AttachMainWindow(window); - window.Show(); window.NavRoot.PushAsync(page).GetAwaiter().GetResult(); return (window, page); } @@ -125,7 +124,7 @@ public class MainPageButtonsTests /// supported headless pattern (cf. CalculatorTests in the /// Avalonia.Samples repo). Returns the nav-stack count /// before the click so the caller can assert on the delta. - /// KeyPressQwerty is dispatched on the + /// KeyPressQwerty is dispatched on the /// itself — it is the that owns the /// headless implementation, and routing the key through any /// descendant TopLevel (e.g. one obtained via @@ -134,7 +133,7 @@ public class MainPageButtonsTests /// because the descendant does not carry the /// PlatformHandle the harness expects. /// - private static int ClickAndCapture(MainWindow window, Button button) + private static int ClickAndCapture(MainView window, Button button) { var stackBefore = window.NavRoot.NavigationStack.Count; button.Command?.Execute(button.CommandParameter); diff --git a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs index d13468f0..ccb33ec7 100644 --- a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs +++ b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs @@ -117,7 +117,7 @@ public class PostAclDialogTests /// rebinding the global DI mid-test would trample the /// Settings singleton the rest of the harness depends on. /// - private static (MainWindow window, BlogAclApiClient aclClient, CircleApiClient circleClient, CountingHttpHandler handler) Mount() + private static (MainView window, BlogAclApiClient aclClient, CircleApiClient circleClient, CountingHttpHandler handler) Mount() { var handler = new CountingHttpHandler(); var settings = new Settings(); @@ -138,10 +138,9 @@ public class PostAclDialogTests // CountingHttpHandler. GC.KeepAlive(sp); - var window = new MainWindow(); + var window = new MainView(); var app = (App)Application.Current!; app.AttachMainWindow(window); - window.Show(); return (window, aclClient, circleClient, handler); } diff --git a/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs b/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs index e1a5dd19..0a28ee87 100644 --- a/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs +++ b/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs @@ -9,7 +9,7 @@ namespace PostIt.Tests; /// /// UI tests for . Mounted inside -/// a real via the headless Avalonia +/// a real via the headless Avalonia /// platform declared in TestApp.cs. /// /// The pattern is the one that UnitTest1.MainPage_Should_Load @@ -34,9 +34,8 @@ public class SessionStatusBannerTests [AvaloniaFact] public void Banner_renders_three_buttons_in_the_visual_tree() { - var window = new MainWindow(); + var window = new MainView(); window.SessionBanner.DataContext = new SessionStatusViewModel(); - window.Show(); var buttons = window.SessionBanner.GetVisualDescendants() .OfType public IServiceProvider? ServiceProvider { get; private set; } - public MainWindow? Window { get; private set; } + public MainView? View { get; private set; } public override void Initialize() { @@ -44,10 +44,28 @@ public partial class App : Application this.ServiceProvider = new ServiceCollection().BuildServices(); var settings = ServiceProvider.GetRequiredService(); + var sessionStatus = ServiceProvider!.GetRequiredService(); + sessionStatus.LogoutCompleted += () => + { + View.NavRoot.PopToRootAsync(); + }; + sessionStatus.LoginSucceeded += () => + { + PushMainPageAsync(); + }; if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = Window = CreateMainWindow(); + var window = ServiceProvider.GetRequiredService(); + desktop.MainWindow = window; + var api = ServiceProvider!.GetRequiredService(); + desktop.MainWindow.Opened += async (_, _) => await BootAsync(this.ServiceProvider!, api); + + View = window.MainView; + + View.SessionBanner.DataContext = sessionStatus; + // FIXME Window.Opened += async (_, _) => await BootAsync(this.ServiceProvider!, api); + ApplyDarkMode(settings); } else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) @@ -55,54 +73,34 @@ public partial class App : Application singleViewFactoryApplicationLifetime.MainViewFactory = () => { - Window = CreateMainWindow(); + View = ServiceProvider.GetRequiredService(); ApplyDarkMode(settings); - Window.Show(); - return Window; + View.SessionBanner.DataContext = sessionStatus; + return View; }; } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) { - singleViewPlatform.MainView = Window = CreateMainWindow(); + singleViewPlatform.MainView = View = ServiceProvider.GetRequiredService(); + View.SessionBanner.DataContext = sessionStatus; ApplyDarkMode(settings); } var mainVm = ServiceProvider!.GetRequiredService(); base.OnFrameworkInitializationCompleted(); - this.PushPageAsync(mainVm).Wait(); + this.PushPageAsync(mainVm); } - - private MainWindow CreateMainWindow() - { - Window = new MainWindow(); - var api = ServiceProvider!.GetRequiredService(); - Window.Opened += async (_, _) => await BootAsync(this.ServiceProvider!, api); - var sessionStatus = ServiceProvider!.GetRequiredService(); - sessionStatus.LogoutCompleted += () => - { - Window.NavRoot.PopToRootAsync(); - }; - - sessionStatus.LoginSucceeded += () => - { - PushMainPageAsync(); - }; - - Window.SessionBanner.DataContext = sessionStatus; - return Window; - } - /// - /// Test-only hook: bind a concrete so + /// Test-only hook: bind a concrete so /// command-driven navigation paths () can /// push onto a real in headless /// fixtures that do not run the full desktop lifetime bootstrap. /// - internal void AttachMainWindow(MainWindow mainWindow) + internal void AttachMainWindow(MainView mainWindow) { - Window = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); + View = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); } private static void ApplyDarkMode(Settings settings) @@ -180,6 +178,6 @@ public partial class App : Application internal async Task GoBackAsync() { - await Window!.NavRoot.PopAsync(); + await View!.NavRoot.PopAsync(); } } diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs index fae24fa6..c4e774cc 100644 --- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs @@ -23,11 +23,13 @@ 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 contactService = new ContactService(); var userDirectory = new UserDirectory(userSearchClient); // Vues - services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + // SettingsPage is a singleton: there must be one and only one // instance of the settings UI for the lifetime of the app. // This guarantees that (a) the bindings always reflect the @@ -58,7 +60,6 @@ public static class ServiceCollectionHelpers services.AddSingleton(circleClient); services.AddSingleton(blogAclClient); services.AddSingleton(userSearchClient); - services.AddSingleton(contactService); services.AddSingleton(userDirectory); services.AddTransient(); services.AddTransient(); diff --git a/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs b/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs index 3cb00301..e4250cbb 100644 --- a/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs @@ -10,7 +10,7 @@ public static class ViewModelBaseHelpers { public static async Task PushPageAsync(this App app, ViewModelBase vm) { - var window = app.Window; + var window = app.View; if (window is null) { throw new InvalidOperationException("MainWindow is not initialized yet."); diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index 70bd72ca..bde8fd41 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -28,7 +28,6 @@ All - diff --git a/src/PostIt/PostIt/Services/ContactService.Desktop.cs b/src/PostIt/PostIt/Services/ContactService.Desktop.cs deleted file mode 100644 index fa7d37f6..00000000 --- a/src/PostIt/PostIt/Services/ContactService.Desktop.cs +++ /dev/null @@ -1,36 +0,0 @@ -#if !ANDROID && !IOS -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace PostIt.Services; - -/// -/// Desktop stub for . -/// -/// The desktop has no equivalent of the mobile address -/// book (no Contacts.Default, no CardDAV out of the -/// box). Rather than synthesise a list from a different -/// source, this provider returns an empty list and lets the -/// UI render an honest "no local contacts on this platform" -/// message. -/// -/// If desktop users want to invite people who aren't -/// Yavsc members, that flow goes through a separate path -/// (manual email entry + invitation endpoint) — not through -/// . Finding existing Yavsc -/// members is 's job, not this -/// one's. -/// -/// Future CardDAV / Google Contacts / Exchange -/// providers can plug in here as additional -/// implementations selected -/// from DI by configuration. -/// -public sealed class ContactService : IContactService -{ - public Task> GetDeviceContactsAsync(CancellationToken ct = default) - => Task.FromResult>(Array.Empty()); -} -#endif diff --git a/src/PostIt/PostIt/Views/MainPage.axaml b/src/PostIt/PostIt/Views/MainPage.axaml index fd75d403..e44f9f99 100644 --- a/src/PostIt/PostIt/Views/MainPage.axaml +++ b/src/PostIt/PostIt/Views/MainPage.axaml @@ -97,23 +97,15 @@ - - + diff --git a/src/PostIt/PostIt/Views/MainView.axaml b/src/PostIt/PostIt/Views/MainView.axaml new file mode 100644 index 00000000..643c0f20 --- /dev/null +++ b/src/PostIt/PostIt/Views/MainView.axaml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/src/PostIt/PostIt/Views/MainView.axaml.cs b/src/PostIt/PostIt/Views/MainView.axaml.cs new file mode 100644 index 00000000..74f46686 --- /dev/null +++ b/src/PostIt/PostIt/Views/MainView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace PostIt.Views; + +public partial class MainView : UserControl +{ + public MainView() + { + InitializeComponent(); + } +} diff --git a/src/PostIt/PostIt/Views/MainWindow.axaml b/src/PostIt/PostIt/Views/MainWindow.axaml index 060e0944..e606cf2b 100644 --- a/src/PostIt/PostIt/Views/MainWindow.axaml +++ b/src/PostIt/PostIt/Views/MainWindow.axaml @@ -7,11 +7,6 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="PostIt.Views.MainWindow" Icon="/Assets/avalonia-logo.ico" - Title="PostIt" > - - - - - + Title="PostIt"> + diff --git a/src/PostIt/PostIt/Views/MainWindow.axaml.cs b/src/PostIt/PostIt/Views/MainWindow.axaml.cs index 9cb85f55..0dd9280a 100644 --- a/src/PostIt/PostIt/Views/MainWindow.axaml.cs +++ b/src/PostIt/PostIt/Views/MainWindow.axaml.cs @@ -8,4 +8,4 @@ public partial class MainWindow : Window { InitializeComponent(); } -} \ No newline at end of file +} diff --git a/src/PostIt/PostIt/Views/TextEditorBinding.cs b/src/PostIt/PostIt/Views/TextEditorBinding.cs deleted file mode 100644 index 56fda68c..00000000 --- a/src/PostIt/PostIt/Views/TextEditorBinding.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Avalonia; -using Avalonia.Data; -using AvaloniaEdit; - -namespace PostIt.Views; - -public sealed class TextEditorBinding -{ - public static readonly AttachedProperty TextProperty = - AvaloniaProperty.RegisterAttached( - "Text", - defaultBindingMode: BindingMode.TwoWay); - - private static readonly AttachedProperty IsTextChangeSubscribedProperty = - AvaloniaProperty.RegisterAttached("IsTextChangeSubscribed"); - - static TextEditorBinding() - { - TextProperty.Changed.AddClassHandler((editor, args) => - { - EnsureTextChangeSubscribed(editor); - - var text = args.GetNewValue() ?? string.Empty; - if (editor.Text != text) - { - editor.Text = text; - } - }); - } - - public static string? GetText(TextEditor editor) => editor.GetValue(TextProperty); - - public static void SetText(TextEditor editor, string? value) => editor.SetValue(TextProperty, value); - - private static void EnsureTextChangeSubscribed(TextEditor editor) - { - if (editor.GetValue(IsTextChangeSubscribedProperty)) - { - return; - } - - editor.SetValue(IsTextChangeSubscribedProperty, true); - editor.TextChanged += (_, _) => - { - if (editor.Text != GetText(editor)) - { - SetText(editor, editor.Text); - } - }; - } -} From dd5904f0a7324f8534ccb9d6518095e467ad984d Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 14:53:25 +0100 Subject: [PATCH 13/95] refacto Makefiles and MainView setup --- Makefile | 171 +------------------------------- src/PostIt/Makefile | 173 ++++++++++++++++++++++++++++++++- src/PostIt/PostIt/App.axaml.cs | 39 +++++--- 3 files changed, 197 insertions(+), 186 deletions(-) diff --git a/Makefile b/Makefile index 9f1eca7f..fa9d4ecf 100644 --- a/Makefile +++ b/Makefile @@ -121,173 +121,4 @@ release: git push -u origin "$$BRANCH"; \ echo "==> Terminé. Branche $$BRANCH live sur origin." -# Cibles pour installer PostIt.Android en Debug sur l'AVD qemu. -# -# Usage typique : -# make qemu # lance l'AVD, attend le boot, build l'APK, l'installe -# make android-install # (re)build l'APK et l'installe (AVD doit tourner) -# make android-build # build l'APK seul (sans install) -# make qemu-run # démarre l'AVD en background -# make qemu-stop # arrête l'émulateur -# make qemu-wait-boot # attend que l'AVD ait fini de booter -# -# Variables surchargeables (make VAR=valeur) : -# AVD_NAME default: postit_test_avd -# (l'AVD doit être listé par `avdmanager list avd`) -# ADB_SERIAL default: emulator-5554 -# (port standard du premier émulateur lancé) -# ANDROID_HOME default: /opt/android-sdk -# (le SDK Android local; doit contenir -# emulator/emulator et platform-tools/adb) -# POSTIT_RID default: android-x64 -# (doit matcher l'ABI de l'AVD; `avdmanager list avd` -# affiche la ligne Tag/ABI) -# EMU_HEADLESS default: 0 -# (1 = lancer l'émulateur sans fenêtre, pour scripter) -# CONFIG surcharge la variable CONFIG globale (Debug par -# défaut dans ce Makefile). Passer à Release pour -# un APK optimisé et signé release. -# LOGCAT_LINES default: 200 -# (nombre de lignes dumpées par `make qemu-logcat`) -# LOGCAT_FOLLOW default: 0 -# (1 = stream live via `make logcat`, -# sinon dump one-shot des N dernières lignes) -# LOGCAT_BOOT_WAIT default: 30 -# (secondes d'attente entre le clear du buffer, -# le `am start`, et le dump final dans -# `make qemu-logcat-boot`) -AVD_NAME ?= postit_test_avd -ADB_SERIAL ?= emulator-5554 -ANDROID_HOME ?= /opt/android-sdk -POSTIT_RID ?= android-x64 -EMU_HEADLESS ?= 0 -LOGCAT_LINES ?= 600 -LOGCAT_FOLLOW ?= 0 -LOGCAT_BOOT_WAIT ?= 30 - -ANDROID_PACKAGE_NAME = fr.pschneider.postit -POSTIT_ANDROID_CSPROJ := src/PostIt/PostIt.Android/PostIt.Android.csproj -POSTIT_APK_DIR := src/PostIt/PostIt.Android/bin/$(CONFIG)/net10.0-android/$(POSTIT_RID) -POSTIT_APK := $(POSTIT_APK_DIR)/$(ANDROID_PACKAGE_NAME)-Signed.apk - -qemu-run: - @echo " Starting AVD $(AVD_NAME) on $(ADB_SERIAL)..." - @mkdir -p /tmp/yavsc-emu - @EMU_ARGS=""; \ - if [ "$(EMU_HEADLESS)" = "1" ]; then EMU_ARGS="-no-window -no-audio"; fi; \ - $(ANDROID_HOME)/emulator/emulator -avd $(AVD_NAME) $$EMU_ARGS \ - >/tmp/yavsc-emu/$(AVD_NAME).log 2>&1 & \ - echo " emulator PID: $$!" - -qemu-stop: - adb -s $(ADB_SERIAL) emu kill - -qemu-wait-boot: - @echo " Waiting for $(ADB_SERIAL) to finish booting..." - adb -s $(ADB_SERIAL) wait-for-device - @for i in $$(seq 1 180); do \ - BOOTED=$$(adb -s $(ADB_SERIAL) shell getprop sys.boot_completed 2>/dev/null | tr -d '\r\n'); \ - if [ "$$BOOTED" = "1" ]; then \ - echo " ✓ booted in $${i}s"; \ - exit 0; \ - fi; \ - sleep 1; \ - done; \ - echo " ERROR: device did not boot within 180s." >&2; \ - echo " Logs: /tmp/yavsc-emu/$(AVD_NAME).log" >&2; \ - exit 1 - -android-build: - # EmbedAssembliesIntoApk=true: without this, the Debug APK ships - # without the managed assemblies in it (they are pushed at runtime - # via `adb push`, "Fast Deployment"). On the qemu emulator, the - # runtime cannot find them in `files/.__override__//` and - # aborts at startup with "No assemblies found in '.__override__'" - # (monodroid-glue.cc:757, SIGABRT). Forcing this property on - # packages the .dlls into the APK as `assemblies//` so the - # runtime reads them directly. - # - # The Xamarin.Android SDK property is `EmbedAssembliesIntoApk`, - # not `AndroidEnableFastDeployment` (which exists in older - # templates but is a no-op in the .NET 10 SDK). - dotnet build $(POSTIT_ANDROID_CSPROJ) \ - -c $(CONFIG) \ - -p:RuntimeIdentifier=$(POSTIT_RID) \ - -p:EmbedAssembliesIntoApk=true \ - --nologo - @if [ ! -f "$(POSTIT_APK)" ]; then \ - echo " APK not found at $(POSTIT_APK)." >&2; \ - echo " Files in $(POSTIT_APK_DIR):" >&2; \ - ls -la "$(POSTIT_APK_DIR)" 2>/dev/null || echo " (directory does not exist)" >&2; \ - exit 1; \ - fi - - -android-install: android-build - @echo " Installing $(POSTIT_APK) on $(ADB_SERIAL)..." - adb -s $(ADB_SERIAL) install -r "$(POSTIT_APK)" -r - -qemu-uninstall: - adb -s $(ADB_SERIAL) uninstall $(ANDROID_PACKAGE_NAME) - -# Dump recent logcat output for the running PostIt.Android process. -# By default, prints the last $(LOGCAT_LINES) lines (one-shot, with -# `-d`). Set LOGCAT_FOLLOW=1 to follow the stream live instead. -# Filtering is by PID (pidof $(ANDROID_PACKAGE_NAME)), not by tag, -# because Mono/Xamarin can emit logs under several tags -# (mono, PostIt.Android, Avalonia.Android) and tag-based filtering -# would miss the ones not matching. PID-based filtering is exact. -# If the app is not running, pidof returns empty and logcat exits -# silently with no output; that is the expected behaviour for -# "no logs yet". -logcat: - @PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \ - if [ -z "$$PID" ]; then \ - echo " $(ANDROID_PACKAGE_NAME) is not running on $(ADB_SERIAL)."; \ - echo " Start the app first (am start -n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity)"; \ - exit 1; \ - fi; \ - echo " Following PID $$PID (LOGCAT_FOLLOW=$(LOGCAT_FOLLOW), LOGCAT_LINES=$(LOGCAT_LINES))"; \ - if [ "$(LOGCAT_FOLLOW)" = "1" ]; then \ - adb -s $(ADB_SERIAL) logcat -v time --pid=$$PID $(ANDROID_PACKAGE_NAME); \ - else \ - adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID $(ANDROID_PACKAGE_NAME); \ - fi - -# Clear logcat, launch PostIt.Android, then dump everything that was -# emitted during the startup window. Targets the "démarrage KO" case -# where the process starts but Avalonia never renders a frame — the -# logcat trace from process start to first frame is what diagnoses it. -# -# Override LOGCAT_BOOT_WAIT to extend the post-launch wait -# (default 15s; raise to 30+ if the device is slow to boot Avalonia). -LOGCAT_BOOT_WAIT ?= 15 - - -android-start: - @echo " Clearing logcat buffer..." - adb -s $(ADB_SERIAL) logcat -c - @echo " Launching $(ANDROID_PACKAGE_NAME)..." - adb -s $(ADB_SERIAL) shell am start \ - -n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity - -qemu-logcat-boot: android-start - @echo " Waiting $(LOGCAT_BOOT_WAIT)s for the app to start rendering..." - @sleep $(LOGCAT_BOOT_WAIT) - - @echo " Dumping logcat (PostIt PID + system buffer):" - @PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \ - if [ -n "$$PID" ]; then \ - echo " ✅ (PID $$PID at dump time)"; \ - sleep 10; \ - adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID; \ - else \ - echo " 👿 (PostIt process not running at dump time — dumping last $(LOGCAT_LINES) lines unfiltered)"; \ - adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES); \ - exit 1; \ - fi - -qemu: qemu-run qemu-wait-boot android-install - @echo " ✓ PostIt.Android installed on $(ADB_SERIAL)" - -.PHONY: test release qemu qemu-run qemu-stop qemu-wait-boot android-build android-install logcat qemu-logcat-boot +.PHONY: test release diff --git a/src/PostIt/Makefile b/src/PostIt/Makefile index 72158bfa..1217976b 100644 --- a/src/PostIt/Makefile +++ b/src/PostIt/Makefile @@ -1,7 +1,178 @@ +# Cibles pour installer PostIt.Android en Debug sur l'AVD qemu. +# +# Usage typique : +# make qemu # lance l'AVD, attend le boot, build l'APK, l'installe +# make android-install # (re)build l'APK et l'installe (AVD doit tourner) +# make android-build # build l'APK seul (sans install) +# make qemu-run # démarre l'AVD en background +# make qemu-stop # arrête l'émulateur +# make qemu-wait-boot # attend que l'AVD ait fini de booter +# +# Variables surchargeables (make VAR=valeur) : +# AVD_NAME default: postit_test_avd +# (l'AVD doit être listé par `avdmanager list avd`) +# ADB_SERIAL default: emulator-5554 +# (port standard du premier émulateur lancé) +# ANDROID_HOME default: /opt/android-sdk +# (le SDK Android local; doit contenir +# emulator/emulator et platform-tools/adb) +# POSTIT_RID default: android-x64 +# (doit matcher l'ABI de l'AVD; `avdmanager list avd` +# affiche la ligne Tag/ABI) +# EMU_HEADLESS default: 0 +# (1 = lancer l'émulateur sans fenêtre, pour scripter) +# CONFIG surcharge la variable CONFIG globale (Debug par +# défaut dans ce Makefile). Passer à Release pour +# un APK optimisé et signé release. +# LOGCAT_LINES default: 200 +# (nombre de lignes dumpées par `make qemu-logcat`) +# LOGCAT_FOLLOW default: 0 +# (1 = stream live via `make logcat`, +# sinon dump one-shot des N dernières lignes) +# LOGCAT_BOOT_WAIT default: 30 +# (secondes d'attente entre le clear du buffer, +# le `am start`, et le dump final dans +# `make qemu-logcat-boot`) +AVD_NAME ?= postit_test_avd +ADB_SERIAL ?= emulator-5554 +ANDROID_HOME ?= /opt/android-sdk +POSTIT_RID ?= android-x64 +EMU_HEADLESS ?= 0 +LOGCAT_LINES ?= 600 +LOGCAT_FOLLOW ?= 0 +LOGCAT_BOOT_WAIT ?= 30 + +ANDROID_PACKAGE_NAME = fr.pschneider.postit +POSTIT_ANDROID_CSPROJ := PostIt.Android/PostIt.Android.csproj +POSTIT_APK_DIR := PostIt.Android/bin/$(CONFIG)/net10.0-android/$(POSTIT_RID) +POSTIT_APK := $(POSTIT_APK_DIR)/$(ANDROID_PACKAGE_NAME)-Signed.apk + clean: clean-PostIt clean-PostIt.Android clean-PostIt.Desktop clean-%: rm -rf $*/obj $*/bin -.PHONY: clean +qemu-run: + @echo " Starting AVD $(AVD_NAME) on $(ADB_SERIAL)..." + @mkdir -p /tmp/yavsc-emu + @EMU_ARGS=""; \ + if [ "$(EMU_HEADLESS)" = "1" ]; then EMU_ARGS="-no-window -no-audio"; fi; \ + $(ANDROID_HOME)/emulator/emulator -avd $(AVD_NAME) $$EMU_ARGS \ + >/tmp/yavsc-emu/$(AVD_NAME).log 2>&1 & \ + echo " ✅ Started emulator PID: $$!" + +qemu-stop: + adb -s $(ADB_SERIAL) emu kill + echo " ✅ Stopped emulator" + +qemu-wait-boot: + @echo " Waiting for $(ADB_SERIAL) to finish booting..." + adb -s $(ADB_SERIAL) wait-for-device + @for i in $$(seq 1 180); do \ + BOOTED=$$(adb -s $(ADB_SERIAL) shell getprop sys.boot_completed 2>/dev/null | tr -d '\r\n'); \ + if [ "$$BOOTED" = "1" ]; then \ + echo " ✓ booted in $${i}s"; \ + exit 0; \ + fi; \ + sleep 1; \ + done; \ + echo " 👿 ERROR: device did not boot within 180s." >&2; \ + echo " Logs: /tmp/yavsc-emu/$(AVD_NAME).log" >&2; \ + exit 1 + +android-build: + # EmbedAssembliesIntoApk=true: without this, the Debug APK ships + # without the managed assemblies in it (they are pushed at runtime + # via `adb push`, "Fast Deployment"). On the qemu emulator, the + # runtime cannot find them in `files/.__override__//` and + # aborts at startup with "No assemblies found in '.__override__'" + # (monodroid-glue.cc:757, SIGABRT). Forcing this property on + # packages the .dlls into the APK as `assemblies//` so the + # runtime reads them directly. + # + # The Xamarin.Android SDK property is `EmbedAssembliesIntoApk`, + # not `AndroidEnableFastDeployment` (which exists in older + # templates but is a no-op in the .NET 10 SDK). + dotnet build $(POSTIT_ANDROID_CSPROJ) \ + -c $(CONFIG) \ + -p:RuntimeIdentifier=$(POSTIT_RID) \ + -p:EmbedAssembliesIntoApk=true \ + --nologo + @if [ ! -f "$(POSTIT_APK)" ]; then \ + echo " APK not found at $(POSTIT_APK)." >&2; \ + echo " Files in $(POSTIT_APK_DIR):" >&2; \ + ls -la "$(POSTIT_APK_DIR)" 2>/dev/null || echo " (directory does not exist)" >&2; \ + exit 1; \ + fi + + +android-install: android-build + @echo " Installing $(POSTIT_APK) on $(ADB_SERIAL)..." + adb -s $(ADB_SERIAL) install -r "$(POSTIT_APK)" -r + @echo " ✅ PostIt.Android installed on $(ADB_SERIAL)" + +qemu-uninstall: + adb -s $(ADB_SERIAL) uninstall $(ANDROID_PACKAGE_NAME) + +# Dump recent logcat output for the running PostIt.Android process. +# By default, prints the last $(LOGCAT_LINES) lines (one-shot, with +# `-d`). Set LOGCAT_FOLLOW=1 to follow the stream live instead. +# Filtering is by PID (pidof $(ANDROID_PACKAGE_NAME)), not by tag, +# because Mono/Xamarin can emit logs under several tags +# (mono, PostIt.Android, Avalonia.Android) and tag-based filtering +# would miss the ones not matching. PID-based filtering is exact. +# If the app is not running, pidof returns empty and logcat exits +# silently with no output; that is the expected behaviour for +# "no logs yet". +logcat: + @PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \ + if [ -z "$$PID" ]; then \ + echo " $(ANDROID_PACKAGE_NAME) is not running on $(ADB_SERIAL)."; \ + echo " Start the app first (am start -n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity)"; \ + exit 1; \ + fi; \ + echo " Following PID $$PID (LOGCAT_FOLLOW=$(LOGCAT_FOLLOW), LOGCAT_LINES=$(LOGCAT_LINES))"; \ + if [ "$(LOGCAT_FOLLOW)" = "1" ]; then \ + adb -s $(ADB_SERIAL) logcat -v time --pid=$$PID $(ANDROID_PACKAGE_NAME); \ + else \ + adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID $(ANDROID_PACKAGE_NAME); \ + fi + +# Clear logcat, launch PostIt.Android, then dump everything that was +# emitted during the startup window. Targets the "démarrage KO" case +# where the process starts but Avalonia never renders a frame — the +# logcat trace from process start to first frame is what diagnoses it. +# +# Override LOGCAT_BOOT_WAIT to extend the post-launch wait +# (default 15s; raise to 30+ if the device is slow to boot Avalonia). +LOGCAT_BOOT_WAIT ?= 15 + + +android-start: + @echo " Clearing logcat buffer..." + adb -s $(ADB_SERIAL) logcat -c + @echo " Launching $(ANDROID_PACKAGE_NAME)..." + adb -s $(ADB_SERIAL) shell am start \ + -n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity + @echo " ✅ $(ANDROID_PACKAGE_NAME) started on $(ADB_SERIAL)" + +qemu-logcat-boot: android-start + @echo " Waiting $(LOGCAT_BOOT_WAIT)s for the app to start rendering..." + @sleep $(LOGCAT_BOOT_WAIT) + + @echo " Dumping logcat (PostIt PID + system buffer):" + @PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \ + if [ -n "$$PID" ]; then \ + echo " ✅ (PID $$PID at dump time)"; \ + sleep 10; \ + adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID; \ + else \ + echo " 👿 (PostIt process not running at dump time — dumping last $(LOGCAT_LINES) lines unfiltered)"; \ + adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES); \ + exit 1; \ + fi + +qemu: qemu-run qemu-wait-boot android-install + +.PHONY: clean qemu qemu-run qemu-stop qemu-wait-boot android-build android-install logcat qemu-logcat-boot diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index 67d6dac2..f8430347 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -44,27 +44,15 @@ public partial class App : Application this.ServiceProvider = new ServiceCollection().BuildServices(); var settings = ServiceProvider.GetRequiredService(); - var sessionStatus = ServiceProvider!.GetRequiredService(); - sessionStatus.LogoutCompleted += () => - { - View.NavRoot.PopToRootAsync(); - }; - sessionStatus.LoginSucceeded += () => - { - PushMainPageAsync(); - }; if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { var window = ServiceProvider.GetRequiredService(); desktop.MainWindow = window; var api = ServiceProvider!.GetRequiredService(); desktop.MainWindow.Opened += async (_, _) => await BootAsync(this.ServiceProvider!, api); - View = window.MainView; - - View.SessionBanner.DataContext = sessionStatus; - // FIXME Window.Opened += async (_, _) => await BootAsync(this.ServiceProvider!, api); + this.ConfigureRootView(window.MainView); ApplyDarkMode(settings); } @@ -74,15 +62,15 @@ public partial class App : Application () => { View = ServiceProvider.GetRequiredService(); + this.ConfigureRootView(View); ApplyDarkMode(settings); - View.SessionBanner.DataContext = sessionStatus; return View; }; } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) { singleViewPlatform.MainView = View = ServiceProvider.GetRequiredService(); - View.SessionBanner.DataContext = sessionStatus; + ConfigureRootView(View); ApplyDarkMode(settings); } var mainVm = ServiceProvider!.GetRequiredService(); @@ -91,6 +79,27 @@ public partial class App : Application this.PushPageAsync(mainVm); } +private void ConfigureRootView(MainView rootView) +{ + var api = ServiceProvider!.GetRequiredService(); + + // Déclencher le Boot lors du chargement du contrôle à l'écran + rootView.AttachedToVisualTree += async (_, _) => await BootAsync(this.ServiceProvider!, api); + + var sessionStatus = ServiceProvider!.GetRequiredService(); + sessionStatus.LogoutCompleted += () => + { + // Remplacer Window.NavRoot par rootView.NavRoot + rootView.NavRoot.PopToRootAsync(); + }; + + sessionStatus.LoginSucceeded += () => + { + PushMainPageAsync(); + }; + + rootView.SessionBanner.DataContext = sessionStatus; +} /// /// Test-only hook: bind a concrete so From 75b298b0f80c1c1768290c248efdbb810025004c Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 19:20:03 +0100 Subject: [PATCH 14/95] Fixes the Android Login process --- .vscode/launch.json | 14 +++---- .vscode/settings.json | 2 + .vscode/tasks.json | 4 +- src/PostIt/PostIt.Android/MainActivity.cs | 16 +++++--- .../PostIt.Android/PlatformBootstrap.cs | 7 +--- .../Properties/AndroidManifest.xml | 26 ------------- .../Services/AndroidSystemBrowser.cs | 10 ++++- .../Services/OidcCallbackManager.cs | 21 ++++++++++ .../WebAuthenticationCallbackActivity.cs | 35 +++++++++++++++++ .../PostIt.Desktop/PlatformBootstrap.cs | 29 -------------- src/PostIt/PostIt.Desktop/Program.cs | 2 - src/PostIt/PostIt.Tests/SettingsLoadTests.cs | 2 +- src/PostIt/PostIt/App.axaml.cs | 10 ++--- src/PostIt/PostIt/Services/Platform.cs | 6 +-- .../PostIt/Settings/AuthenticationSettings.cs | 12 ++++-- src/PostIt/PostIt/ViewModels/Settings.cs | 39 ++----------------- 16 files changed, 106 insertions(+), 129 deletions(-) create mode 100644 src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs create mode 100644 src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs delete mode 100644 src/PostIt/PostIt.Desktop/PlatformBootstrap.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index 36dc17a2..dc8d3c68 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,19 +5,19 @@ "version": "0.2.0", "configurations": [ { - "name": "Android", + "name": "Android Debug", "type": "mono", "preLaunchTask": "run-debug-android", "request": "attach", "address": "localhost", - "port": 10000 + "port": 55555 }, { - "name": "Attach - Android", + "name": "Android Attach - Debug", "type": "mono", "request": "attach", "address": "localhost", - "port": 10000 + "port": 55555 }, { "name": "API", @@ -26,19 +26,19 @@ "projectPath": "${workspaceFolder}/src/Api/Api.csproj" }, { - "name": "Yavsc.Org", + "name": "Yavsc Org", "type": "dotnet", "request": "launch", "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj", }, { - "name": "Yavsc.Blogs", + "name": "Yavsc Blogs", "type": "dotnet", "request": "launch", "projectPath": "${workspaceFolder}/src/Yavsc.Blogs/Yavsc.Blogs.csproj" }, { - "name": "PostIt", + "name": "PostIt Desktop", "type": "dotnet", "request": "launch", "projectPath": "${workspaceFolder}/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj", diff --git a/.vscode/settings.json b/.vscode/settings.json index 16bbe483..915683fc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "appsettings", "asciidoctor", "ASPNETCORE", + "Avalonia", "Configurabilité", "Cratie", "DESTDIR", @@ -15,6 +16,7 @@ "Hsts", "Newtonsoft", "Npgsql", + "Oidc", "PKCE", "postit", "pschneider", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9fe9caaf..3c0c0bf2 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -18,8 +18,8 @@ "-p:TargetFramework=net10.0-android", "-p:Configuration=Debug", "-p:AndroidAttachDebugger=true", - "-p:AndroidSdbHostPort=10000", - "-p:AndroidSdbTargetPort=10000" + "-p:AndroidSdbHostPort=55555", + "-p:AndroidSdbTargetPort=55555" ], "problemMatcher": [ { diff --git a/src/PostIt/PostIt.Android/MainActivity.cs b/src/PostIt/PostIt.Android/MainActivity.cs index 4f788550..ad8455ef 100644 --- a/src/PostIt/PostIt.Android/MainActivity.cs +++ b/src/PostIt/PostIt.Android/MainActivity.cs @@ -1,13 +1,11 @@ -using Android; + using Android.App; using Android.Content; using Android.Content.PM; using AndroidX.Core.Provider; using AndroidX.Emoji2.Text; -using Avalonia; using Avalonia.Android; -using AndroidX.Core.Provider; -using AndroidX.Emoji2.Text; +using PostIt.Droid.Services; namespace PostIt.Android; @@ -34,7 +32,7 @@ public class MainActivity : AvaloniaMainActivity Yavsc.Resource.Array.com_google_android_gms_fonts_certs); //com_google_android_gms_fonts_certs EmojiCompat.Config config = new FontRequestEmojiCompatConfig(this, fontRequest); EmojiCompat.Init(config); - PlatformBootstrap.EnsureInitialized(); + PlatformBootstrap.InitPlatform(); base.OnCreate(savedInstanceState); Current = this; } @@ -50,7 +48,13 @@ public class MainActivity : AvaloniaMainActivity protected override void OnNewIntent(Intent? intent) { base.OnNewIntent(intent); - if (intent is not null) AndroidOidcCallbackSink.Handle(intent); + + var url = intent?.DataString; + if (!string.IsNullOrEmpty(url) && url.StartsWith("postit://callback")) + { + OidcCallbackManager.SetResult(url); + } + } internal static class AndroidOidcCallbackSink diff --git a/src/PostIt/PostIt.Android/PlatformBootstrap.cs b/src/PostIt/PostIt.Android/PlatformBootstrap.cs index d59b154f..f208f9ce 100644 --- a/src/PostIt/PostIt.Android/PlatformBootstrap.cs +++ b/src/PostIt/PostIt.Android/PlatformBootstrap.cs @@ -12,14 +12,9 @@ namespace PostIt.Android; /// internal static class PlatformBootstrap { - private static int _initialized; - - internal static void EnsureInitialized() + internal static void InitPlatform() { - if (System.Threading.Interlocked.Exchange(ref _initialized, 1) != 0) - return; - Platform.DefaultRedirectUri = ViewModels.Settings.AndroidRedirectUri; Platform.CreateBrowser = () => { var activity = MainActivity.Current; diff --git a/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml b/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml index 91b61d05..8793aae8 100644 --- a/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml +++ b/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml @@ -2,31 +2,5 @@ - - - - - - - - - - diff --git a/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs b/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs index b6716e22..bb10b364 100644 --- a/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs +++ b/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Android.App; using AndroidX.Browser.CustomTabs; using IdentityModel.OidcClient.Browser; +using PostIt.Droid.Services; namespace PostIt.Android.Services; @@ -35,9 +36,14 @@ public sealed class AndroidSystemBrowser : IBrowser }; } - var uri = global::Android.Net.Uri.Parse(options.StartUrl)!; + // 1. Enregistrez la tâche avant de lancer le Custom Tab + var callbackTask = OidcCallbackManager.RegisterCallback(cancellationToken); - var callbackTask = MainActivity.AndroidOidcCallbackSink.AwaitNextCallbackAsync(); + // 2. LANCEZ VOTRE CUSTOM TAB ICI (via AndroidX.Browser.CustomTabs) + // ... code pour ouvrir l'URL d'authentification ... + + + var uri = global::Android.Net.Uri.Parse(options.StartUrl)!; var tabsIntent = new CustomTabsIntent.Builder() .SetShowTitle(true)! diff --git a/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs b/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs new file mode 100644 index 00000000..30f8fa18 --- /dev/null +++ b/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs @@ -0,0 +1,21 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace PostIt.Droid.Services; + +public static class OidcCallbackManager +{ + private static TaskCompletionSource? _tcs; + + public static Task RegisterCallback(CancellationToken cancellationToken) + { + _tcs = new TaskCompletionSource(); + cancellationToken.Register(() => _tcs.TrySetCanceled()); + return _tcs.Task; + } + + public static void SetResult(string url) + { + _tcs?.TrySetResult(url); + } +} diff --git a/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs b/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs new file mode 100644 index 00000000..9ed2eb18 --- /dev/null +++ b/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs @@ -0,0 +1,35 @@ +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.OS; +using PostIt.Droid.Services; + +namespace PostIt.Android; + +[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)] +[IntentFilter(new[] { Intent.ActionView }, + Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, + DataScheme = "postit", // Remplacez par votre schéma personnalisé (ex: yavsc ou postit) + DataHost = "callback")] // Correspond à postit://callback +public class WebAuthenticationCallbackActivity : Activity +{ + protected override void OnCreate(Bundle? savedInstanceState) + { + base.OnCreate(savedInstanceState); + + // Capturer l'URL de redirection OIDC + var url = Intent?.DataString; + + if (!string.IsNullOrEmpty(url)) + { + // Transmettre l'URL au gestionnaire partagé pour compléter la Task + OidcCallbackManager.SetResult(url); + } + + // Fermer cette activité transparente et ramener l'application au premier plan + var intent = new Intent(this, typeof(MainActivity)); + intent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop); + StartActivity(intent); + Finish(); + } +} diff --git a/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs b/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs deleted file mode 100644 index ff9ca7f6..00000000 --- a/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs +++ /dev/null @@ -1,29 +0,0 @@ -using PostIt.Services; - -namespace PostIt.Desktop; - -/// -/// One-shot platform bootstrap. Called from Program.Main so that -/// the shared OIDC login path sees a working IBrowser — the -/// custom-scheme browser that hands the OIDC callback off to the -/// running instance through the named pipe. Desktop builds do NOT use -/// a loopback HTTP listener: the postit:// scheme is registered -/// with the OS at install time and the browser is whatever the user -/// has configured to open it. -/// -internal static class PlatformBootstrap -{ - private static int _initialized; - - internal static void EnsureInitialized() - { - if (System.Threading.Interlocked.Exchange(ref _initialized, 1) != 0) - return; - - // Use the custom-scheme redirect on Desktop. Loopback is only - // a fallback for platforms that cannot register postit:// - // (see Settings.DefaultLoopbackRedirectUri for that path). - Platform.DefaultRedirectUri = AuthenticationSettings.DefaultDesktopRedirectUri; - Platform.CustomScheme = "postit"; - } -} diff --git a/src/PostIt/PostIt.Desktop/Program.cs b/src/PostIt/PostIt.Desktop/Program.cs index f22f79b0..0de3bd69 100644 --- a/src/PostIt/PostIt.Desktop/Program.cs +++ b/src/PostIt/PostIt.Desktop/Program.cs @@ -12,8 +12,6 @@ sealed class Program [STAThread] public static void Main(string[] args) { - PlatformBootstrap.EnsureInitialized(); - // Short-circuit 2nd-instance launches (OS handing us the // postit://callback URL) BEFORE Avalonia spins up a window. // If we let Avalonia initialise, the new MainWindow flashes diff --git a/src/PostIt/PostIt.Tests/SettingsLoadTests.cs b/src/PostIt/PostIt.Tests/SettingsLoadTests.cs index dc58e9fc..43021caa 100644 --- a/src/PostIt/PostIt.Tests/SettingsLoadTests.cs +++ b/src/PostIt/PostIt.Tests/SettingsLoadTests.cs @@ -85,7 +85,7 @@ public class SettingsLoadTests bool flip = ((workerId + i) & 1) == 0; settings.DarkMode = flip; settings.Authentication.RedirectUri = - global::AuthenticationSettings.DefaultDesktopRedirectUri; + global::AuthenticationSettings.DesktopRedirectUri; settings.BusinessApiUrl = flip ? "https://a.example.test/api/v1/" diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index f8430347..1b019f6e 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -73,10 +72,8 @@ public partial class App : Application ConfigureRootView(View); ApplyDarkMode(settings); } - var mainVm = ServiceProvider!.GetRequiredService(); base.OnFrameworkInitializationCompleted(); - this.PushPageAsync(mainVm); } private void ConfigureRootView(MainView rootView) @@ -93,9 +90,9 @@ private void ConfigureRootView(MainView rootView) rootView.NavRoot.PopToRootAsync(); }; - sessionStatus.LoginSucceeded += () => + sessionStatus.LoginSucceeded += async () => { - PushMainPageAsync(); + await PushMainPageAsync(); }; rootView.SessionBanner.DataContext = sessionStatus; @@ -133,6 +130,9 @@ private void ConfigureRootView(MainView rootView) var refreshed = await api.TrySilentLoginAsync().ConfigureAwait(true); var sessionStatus = provider.GetRequiredService(); sessionStatus.Refresh(); + var homePage = provider.GetRequiredService(); + var app = (App)Current!; + await app.PushPageAsync(homePage); if (!refreshed) return; await PushMainPageAsync().ConfigureAwait(true); diff --git a/src/PostIt/PostIt/Services/Platform.cs b/src/PostIt/PostIt/Services/Platform.cs index c867c63c..8e5f7e25 100644 --- a/src/PostIt/PostIt/Services/Platform.cs +++ b/src/PostIt/PostIt/Services/Platform.cs @@ -21,14 +21,14 @@ public static class Platform /// override this property at startup (e.g. PostIt.Android sets /// it to android://postit-signin). /// - public static string DefaultRedirectUri { get; set; } = "postit://callback"; + public const string RedirectUri = "postit://callback"; /// /// Scheme prefix the matches /// against BrowserOptions.EndUrl. Overridable for apps /// that want to register their own scheme. /// - public static string CustomScheme { get; set; } = "postit"; + public const string CustomScheme = "postit"; /// /// Constructs a fresh for the running platform. @@ -37,4 +37,4 @@ public static class Platform /// public static System.Func? CreateBrowser { get; set; } = () => new CustomSchemeBrowser(CustomScheme); -} \ No newline at end of file +} diff --git a/src/PostIt/PostIt/Settings/AuthenticationSettings.cs b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs index 99358cfb..9ebeaebd 100644 --- a/src/PostIt/PostIt/Settings/AuthenticationSettings.cs +++ b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs @@ -10,7 +10,7 @@ public partial class AuthenticationSettings : ObservableObject /// hand-off in /// (RFC 8252 §7.1). Production Desktop builds use this. /// - public const string DefaultDesktopRedirectUri = "postit://callback"; + public const string DesktopRedirectUri = "postit://callback"; /// /// Redirect URI used by the Android app. The corresponding IntentFilter @@ -34,15 +34,19 @@ public partial class AuthenticationSettings : ObservableObject [ObservableProperty] public partial string[] Scopes { get; set; } - /// - /// OAuth redirect URI. Defaults to + /// OAuth redirect URI. Defaults to /// (custom URI scheme) which is the right answer for desktop /// production builds. Mobile platforms must set this to /// before calling LoginAsync. /// [ObservableProperty] - public partial string RedirectUri { get; set; } = DefaultDesktopRedirectUri; + public partial string RedirectUri { get; set; } +#if ANDROID + = AndroidRedirectUri; +#else + = DesktopRedirectUri; +#endif /// /// Space-separated view of . Exists for the diff --git a/src/PostIt/PostIt/ViewModels/Settings.cs b/src/PostIt/PostIt/ViewModels/Settings.cs index 2115a0cd..fd87d772 100644 --- a/src/PostIt/PostIt/ViewModels/Settings.cs +++ b/src/PostIt/PostIt/ViewModels/Settings.cs @@ -2,13 +2,11 @@ using System.Runtime.CompilerServices; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using IdentityModel.OidcClient; -using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Text.Json; -using System.Threading; [assembly: InternalsVisibleTo("PostIt.Tests")] @@ -18,37 +16,6 @@ public partial class Settings : ViewModelBase { const string SettingsFileName = "postit-settings.json"; - /// - /// Redirect URI used by the Android app. The corresponding IntentFilter - /// in PostIt.Android/Properties/AndroidManifest.xml must match. - /// - public const string AndroidRedirectUri = "android://postit-signin"; - - - - /// - /// Process-wide canonical instance, wired up - /// at application boot by - /// through . The hybrid pattern: - /// - /// The static Current reference gives - /// ViewModels a non-DI way to reach the same instance (and lets - /// the framework bindings push notifications through one stable - /// ). - /// Tests that want to exercise a clean - /// instance still call new Settings(); Current - /// stays null in those contexts because - /// is never invoked. - /// Reads () are - /// thread-safe and never allocate; mutations always go through - /// the DI-resolved singleton so two threads cannot each register - /// a different "current" Settings. - /// - /// - private static Settings? s_current; - - - [ObservableProperty] public partial AuthenticationSettings Authentication { get; set; } = new(); @@ -66,7 +33,7 @@ public partial class Settings : ViewModelBase /// setters above all funnel through here, and we flip /// in lock-step. Sub-property mutations /// (e.g. Authentication.Authority) are caught by the - /// subscription wired up in + /// subscription wired up in /// below. disables the flag during bulk /// hydration so the disk load itself does not count as a user /// edit. @@ -336,7 +303,7 @@ public partial class Settings : ViewModelBase this.Authentication.ClientId = string.IsNullOrWhiteSpace(settings.Authentication.ClientId) ? AuthenticationSettings.DefaultClientId : settings.Authentication.ClientId; this.Authentication.RedirectUri = string.IsNullOrWhiteSpace(settings.Authentication.RedirectUri) ? - AuthenticationSettings.DefaultDesktopRedirectUri : settings.Authentication.RedirectUri; + AuthenticationSettings.DesktopRedirectUri : settings.Authentication.RedirectUri; if (settings.Authentication.Scopes is null || settings.Authentication.Scopes.Length == 0) { settings.Authentication.Scopes = AuthenticationSettings.DefaultScopes; @@ -377,7 +344,7 @@ public partial class Settings : ViewModelBase { Authority = AuthenticationSettings.DefaultAuthority, ClientId = AuthenticationSettings.DefaultClientId, - RedirectUri = AuthenticationSettings.DefaultDesktopRedirectUri, + RedirectUri = AuthenticationSettings.DesktopRedirectUri, Scopes = AuthenticationSettings.DefaultScopes }; this.DarkMode = false; From c707b5f29edb6e2b607033a8e9e0683f6b4cc680 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 19:26:52 +0100 Subject: [PATCH 15/95] Release 1.0.8-rc3 - unstable --- .forgejo/workflows/release.yml | 2 +- CHANGELOG.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 09d9757a..d7b12d2e 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -197,7 +197,7 @@ jobs: APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.PostIt-Signed.apk if [[ ! -f "$APK" ]]; then echo "::error::APK not found at $APK" - ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android/ 2>/dev/null || true + ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64 2>/dev/null || true exit 1 fi cp "$APK" /src/_src/PostIt.Android.apk diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a4a88f..acb3c275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,16 +21,17 @@ pour la production des paquets `.deb`. ### Added * a code cleanup -* a failling a disabled test ala Xamarin.UITest : the Android app stil doesn't starts -- Android app does not crash any more, but still no home page, only the splash screnn +* a first Xamarin.UITest is successful +* Android app starts, the login process succeeds ### Changed -mostly nothing +mostly nothing but refactorings ### Fixed -mostly nothing +nothing, [TODO] a bug persists laoding a blog post from PostIt, ACL sould come along with and should not need any "Refresh" button. +Today, assigning ACL succeeds the first time, the following times result in a 409 ## [1.0.8-rc1] - unstable From 6d6c4f2a8a0882e21cc36adaed3ebd51bcac894f Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 20:30:39 +0100 Subject: [PATCH 16/95] Package renamed impact --- .forgejo/workflows/release.yml | 2 +- .github/workflows/docker-publish-android.yml | 2 +- src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index d7b12d2e..cf6594ec 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -194,7 +194,7 @@ jobs: # la racine du checkout pour que l'étape d'upload le trouve. run: | cd /src/_src - APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.PostIt-Signed.apk + APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk if [[ ! -f "$APK" ]]; then echo "::error::APK not found at $APK" ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64 2>/dev/null || true diff --git a/.github/workflows/docker-publish-android.yml b/.github/workflows/docker-publish-android.yml index d560b216..a4ce15aa 100644 --- a/.github/workflows/docker-publish-android.yml +++ b/.github/workflows/docker-publish-android.yml @@ -40,7 +40,7 @@ jobs: - name: Extraire l'APK du conteneur Docker run: | docker create --name extractor postit-android - docker cp extractor:/src/src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.PostIt-Signed.apk ./PostIt.Android.apk + docker cp extractor:/src/src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk ./PostIt.Android.apk docker rm extractor - name: Téléverser l'APK en tant qu'Artéfact GitHub diff --git a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs index 23b423e1..8a256afc 100644 --- a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs +++ b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs @@ -14,7 +14,7 @@ namespace PostIt.Tests; /// public class AndroidAppLaunchTests { - private const string PackageName = "fr.pschneider.PostIt"; + private const string PackageName = "fr.pschneider.postit"; private readonly ITestOutputHelper _output; From 50a07102cbe17e2a617fd294f1ae54ad393a3109 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 21:15:04 +0100 Subject: [PATCH 17/95] Do not fail at attaching developer tools --- src/PostIt/PostIt/App.axaml.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index 1b019f6e..c4db01c4 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -33,7 +33,14 @@ public partial class App : Application { AvaloniaXamlLoader.Load(this); #if DEBUG - this.AttachDeveloperTools(); + try { + this.AttachDeveloperTools(); + } + catch (Exception ex) + { + Console.Error.WriteLine( + $"Could not attache developer tools : {ex.Message}"); + } #endif } From d56ad25e6b36985a26f1e27f69b53b55705b05e6 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 23:01:00 +0100 Subject: [PATCH 18/95] fixes the tests --- .forgejo/workflows/buildAndTest.yml | 25 ++++++++++ .../PostIt.Tests/SessionStatusBannerTests.cs | 48 +++++++++++-------- src/PostIt/PostIt/App.axaml.cs | 14 +----- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index a0f3a375..7bfc0c8b 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -27,6 +27,9 @@ jobs: build: runs-on: docker + container: + # Options cruciales pour donner accès à KVM et autoriser l'émulation + options: --device /dev/kvm --privileged steps: - name: Clone yavsc @@ -44,5 +47,27 @@ jobs: run: cd /src/_src && dotnet restore - name: Build run: cd /src/_src && dotnet build --no-restore + - name: Démarrer l'émulateur QEMU Headless et exécuter les tests + run: | + echo "Démarrage de l'émulateur en tâche de fond..." + # Remplacer 'pixel_5_api_30' par le nom exact de votre AVD créé sur l'hôte + emulator -avd emulator-5554 -no-window -no-audio -no-boot-anim -gpu swiftshader & + EMU_PID=$! + echo "Attente de la détection par ADB..." + adb wait-for-device + echo "Attente de la fin du boot d'Android..." + OUT="" + while [[ "$OUT" != "1" ]]; do + sleep 5 + OUT=$(adb shell getprop sys.boot_completed | tr -d '\r') + echo "Statut actuel : ${OUT:-0}" + done + echo "✅ L'émulateur Android est prêt !" + echo "Installation de l'APK de Debug..." + adb install /src/_src/src/PostIt/PostIt.Android/bin/Debug/net10.0-android/fr.pschneider.postit-Signed.apk + dotnet test /src/_src/src/PostIt/PostIt.Tests/PostIt.Tests.csproj -c Release + - name: Test + echo "⌛ Lancement des tests ..." run: cd /src/_src && dotnet test --no-build --verbosity normal + diff --git a/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs b/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs index 0a28ee87..4d49b865 100644 --- a/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs +++ b/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs @@ -34,10 +34,10 @@ public class SessionStatusBannerTests [AvaloniaFact] public void Banner_renders_three_buttons_in_the_visual_tree() { - var window = new MainView(); - window.SessionBanner.DataContext = new SessionStatusViewModel(); + MainWindow window = new MainWindow(); + window.Show(); - var buttons = window.SessionBanner.GetVisualDescendants() + var buttons = window.GetVisualDescendants() .OfType - internal void AttachMainWindow(MainView mainWindow) + internal void AttachMainWindow(MainView mainView) { - View = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); + View = mainView ?? throw new ArgumentNullException(nameof(mainView)); } private static void ApplyDarkMode(Settings settings) From b18f86ed230dc892f1581c91e8338ff5f31de7d4 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 23:06:25 +0100 Subject: [PATCH 19/95] fixes the script --- .forgejo/workflows/buildAndTest.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 7bfc0c8b..a3f63cef 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -63,11 +63,11 @@ jobs: echo "Statut actuel : ${OUT:-0}" done echo "✅ L'émulateur Android est prêt !" - echo "Installation de l'APK de Debug..." + echo "⌛ Installation de l'APK de Debug..." adb install /src/_src/src/PostIt/PostIt.Android/bin/Debug/net10.0-android/fr.pschneider.postit-Signed.apk dotnet test /src/_src/src/PostIt/PostIt.Tests/PostIt.Tests.csproj -c Release - name: Test - echo "⌛ Lancement des tests ..." - run: cd /src/_src && dotnet test --no-build --verbosity normal - + run: | + echo "⌛ Lancement des tests..." + cd /src/_src && dotnet test --no-build --verbosity normal From 43dd526e4f2f9ce52a6b345961075424ac50088e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 23:07:19 +0100 Subject: [PATCH 20/95] fixes the step title --- .forgejo/workflows/buildAndTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index a3f63cef..8180eead 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -47,7 +47,7 @@ jobs: run: cd /src/_src && dotnet restore - name: Build run: cd /src/_src && dotnet build --no-restore - - name: Démarrer l'émulateur QEMU Headless et exécuter les tests + - name: Démarrer l'émulateur QEMU Headless run: | echo "Démarrage de l'émulateur en tâche de fond..." # Remplacer 'pixel_5_api_30' par le nom exact de votre AVD créé sur l'hôte From 14b0cdb6a4024d885675accdf162ce70f3e6383e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 23:14:50 +0100 Subject: [PATCH 21/95] undated --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acb3c275..bec19e53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ pour la production des paquets `.deb`. ### Changed -mostly nothing but refactorings +L'identifiant de l'application client Android a changé, il passe en minuscules : +`fr.pschneider.postit` ### Fixed From 863c549d51b3e228b5781e056f8077750af33fde Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 23:17:17 +0100 Subject: [PATCH 22/95] reinstall when yet installed --- .forgejo/workflows/buildAndTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 8180eead..1d97bb28 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -64,7 +64,7 @@ jobs: done echo "✅ L'émulateur Android est prêt !" echo "⌛ Installation de l'APK de Debug..." - adb install /src/_src/src/PostIt/PostIt.Android/bin/Debug/net10.0-android/fr.pschneider.postit-Signed.apk + adb install /src/_src/src/PostIt/PostIt.Android/bin/Debug/net10.0-android/fr.pschneider.postit-Signed.apk -r dotnet test /src/_src/src/PostIt/PostIt.Tests/PostIt.Tests.csproj -c Release - name: Test From a4ae0fde728d19cb43ec4d9fde90b04ecda24106 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 25 Aug 2026 23:39:12 +0100 Subject: [PATCH 23/95] should work, Xamarin.UITest installs the app --- .forgejo/workflows/buildAndTest.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 1d97bb28..fe80aba1 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -63,10 +63,6 @@ jobs: echo "Statut actuel : ${OUT:-0}" done echo "✅ L'émulateur Android est prêt !" - echo "⌛ Installation de l'APK de Debug..." - adb install /src/_src/src/PostIt/PostIt.Android/bin/Debug/net10.0-android/fr.pschneider.postit-Signed.apk -r - dotnet test /src/_src/src/PostIt/PostIt.Tests/PostIt.Tests.csproj -c Release - - name: Test run: | echo "⌛ Lancement des tests..." From 0bb293c6eed5718be6d177d76627eb8c70170a8d Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 00:20:17 +0100 Subject: [PATCH 24/95] postit_test_avd --- .forgejo/workflows/buildAndTest.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index fe80aba1..80c6c9e6 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -49,9 +49,10 @@ jobs: run: cd /src/_src && dotnet build --no-restore - name: Démarrer l'émulateur QEMU Headless run: | + set -e echo "Démarrage de l'émulateur en tâche de fond..." # Remplacer 'pixel_5_api_30' par le nom exact de votre AVD créé sur l'hôte - emulator -avd emulator-5554 -no-window -no-audio -no-boot-anim -gpu swiftshader & + emulator -avd postit_test_avd -no-window -no-audio -no-boot-anim -gpu swiftshader & EMU_PID=$! echo "Attente de la détection par ADB..." adb wait-for-device @@ -63,6 +64,7 @@ jobs: echo "Statut actuel : ${OUT:-0}" done echo "✅ L'émulateur Android est prêt !" + - name: Test run: | echo "⌛ Lancement des tests..." From 553bd415a9a4c84d0009bad7df9847553fe2a6f7 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 01:24:19 +0100 Subject: [PATCH 25/95] comment cleanup --- .forgejo/workflows/buildAndTest.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 80c6c9e6..99ec4c0c 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -51,7 +51,6 @@ jobs: run: | set -e echo "Démarrage de l'émulateur en tâche de fond..." - # Remplacer 'pixel_5_api_30' par le nom exact de votre AVD créé sur l'hôte emulator -avd postit_test_avd -no-window -no-audio -no-boot-anim -gpu swiftshader & EMU_PID=$! echo "Attente de la détection par ADB..." From 36d58b6870930a54c2e3954055428ea04cd8b36e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 01:42:59 +0100 Subject: [PATCH 26/95] simpler is better --- .forgejo/workflows/buildAndTest.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 99ec4c0c..bb3065ab 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -42,16 +42,12 @@ jobs: git checkout FETCH_HEAD fi git submodule update --init --recursive - echo "Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: Restore dependencies - run: cd /src/_src && dotnet restore - - name: Build - run: cd /src/_src && dotnet build --no-restore + echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - name: Démarrer l'émulateur QEMU Headless run: | set -e echo "Démarrage de l'émulateur en tâche de fond..." - emulator -avd postit_test_avd -no-window -no-audio -no-boot-anim -gpu swiftshader & + emulator -avd pixel_test -no-window -no-audio -no-boot-anim -gpu swiftshader & EMU_PID=$! echo "Attente de la détection par ADB..." adb wait-for-device @@ -63,8 +59,7 @@ jobs: echo "Statut actuel : ${OUT:-0}" done echo "✅ L'émulateur Android est prêt !" - - name: Test run: | echo "⌛ Lancement des tests..." - cd /src/_src && dotnet test --no-build --verbosity normal + cd /src/_src && dotnet test --verbosity normal From 9d5667c459084ae2852fcc31274cfb583d9b65f0 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 01:49:57 +0100 Subject: [PATCH 27/95] to be clear --- .forgejo/workflows/buildAndTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index bb3065ab..952ac493 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -62,4 +62,4 @@ jobs: - name: Test run: | echo "⌛ Lancement des tests..." - cd /src/_src && dotnet test --verbosity normal + cd /src/_src && dotnet test --verbosity normal && echo "✅ Success !" From dab56e508f166a8bfea249c164251205709d0afc Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 02:24:03 +0100 Subject: [PATCH 28/95] hit the AVD --- .forgejo/workflows/buildAndTest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 952ac493..62f2e9f3 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -46,6 +46,7 @@ jobs: - name: Démarrer l'émulateur QEMU Headless run: | set -e + export ANDROID_AVD_HOME=${ANDROID_HOME}/.android/avd echo "Démarrage de l'émulateur en tâche de fond..." emulator -avd pixel_test -no-window -no-audio -no-boot-anim -gpu swiftshader & EMU_PID=$! From 70ad5100be90180cffd858e77248c84ea1e9a7de Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 03:55:21 +0100 Subject: [PATCH 29/95] yatry --- .forgejo/workflows/buildAndTest.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 62f2e9f3..3c55894d 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -27,9 +27,6 @@ jobs: build: runs-on: docker - container: - # Options cruciales pour donner accès à KVM et autoriser l'émulation - options: --device /dev/kvm --privileged steps: - name: Clone yavsc @@ -47,9 +44,16 @@ jobs: run: | set -e export ANDROID_AVD_HOME=${ANDROID_HOME}/.android/avd - echo "Démarrage de l'émulateur en tâche de fond..." - emulator -avd pixel_test -no-window -no-audio -no-boot-anim -gpu swiftshader & + + adb start-server + echo "⌛ Démarrage de l'émulateur en tâche de fond..." + emulator -avd pixel_test \ + -no-window \ + -no-audio \ + -no-boot-anim \ + -gpu off & EMU_PID=$! + echo "Attente de la détection par ADB..." adb wait-for-device echo "Attente de la fin du boot d'Android..." From a9c18d670812128b0ebaa6c26f3fdc666744f88f Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 04:52:57 +0100 Subject: [PATCH 30/95] emulator --- .forgejo/workflows/buildAndTest.yml | 35 +++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 3c55894d..7b810119 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -25,9 +25,10 @@ on: jobs: build: - runs-on: docker - + container: + image: pazof/yavsc-build-env:debian12-dotnet10-android36-v5 + options: --device /dev/kvm --privileged steps: - name: Clone yavsc run: | @@ -40,30 +41,50 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" + - name: Démarrer l'émulateur QEMU Headless run: | - set -e + # 1. Variables d'environnement pour stabiliser le noyau Android 36 + export RE_EMULATOR_DISABLE_CPUID_CHECK=1 + export ANDROID_EMULATOR_DISABLE_VULKAN=1 + export ANDROID_EMULATOR_FORCE_GLES_2=1 + export ANDROID_HOME=/opt/android-sdk export ANDROID_AVD_HOME=${ANDROID_HOME}/.android/avd + # 2. Initialisation obligatoire du serveur ADB adb start-server + echo "⌛ Démarrage de l'émulateur en tâche de fond..." + + # 3. Lancement corrigé : exclusion de -gpu off (qui brise SwiftShader) et forçage d'un profil CPU standard emulator -avd pixel_test \ -no-window \ -no-audio \ -no-boot-anim \ - -gpu off & - EMU_PID=$! + -gpu swiftshader \ + -no-metrics \ + -qemu -cpu max,fma=off,avx2=off,bmi1=off,bmi2=off & echo "Attente de la détection par ADB..." adb wait-for-device + echo "Attente de la fin du boot d'Android..." OUT="" - while [[ "$OUT" != "1" ]]; do - sleep 5 + COUNTER=0 + # Sécurité : ajout d'un timeout (max 30 essais) pour éviter que la CI ne tourne à l'infini si le boot gèle + while [[ "$OUT" != "1" && $COUNTER -lt 30 ]]; do + sleep 10 OUT=$(adb shell getprop sys.boot_completed | tr -d '\r') echo "Statut actuel : ${OUT:-0}" + let COUNTER=COUNTER+1 done + + if [ "$OUT" != "1" ]; then + echo "❌ Échec du boot d'Android après délai imparti." + exit 1 + fi echo "✅ L'émulateur Android est prêt !" + - name: Test run: | echo "⌛ Lancement des tests..." From 851920c79e0ba4f49deeb44a67c8d7eda4221134 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 05:09:02 +0100 Subject: [PATCH 31/95] specify the docker image --- .forgejo/workflows/buildAndTest.yml | 6 ++++++ .forgejo/workflows/release.yml | 2 ++ 2 files changed, 8 insertions(+) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 7b810119..7b587fb0 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -87,5 +87,11 @@ jobs: - name: Test run: | + echo "⌛ Installation de l'APK de Debug..." + # Ajustez le chemin vers l'APK généré par votre build de debug + cd /src/_src + APK=src/PostIt/PostIt.Android/bin/Debug/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk + adb install ${APK} + echo "⌛ Lancement des tests..." cd /src/_src && dotnet test --verbosity normal && echo "✅ Success !" diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index cf6594ec..a4f86b37 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -51,6 +51,8 @@ jobs: # via l'API REST Forgejo (pas d'actions tierces Node). release: runs-on: docker + container: + image: pazof/yavsc-build-env:debian12-dotnet10-android36-v5 steps: - name: Clone du repo au tag demandé env: From 1a79ee93ebedd72d9abc34d6c9e9a13f7af40a73 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 05:15:13 +0100 Subject: [PATCH 32/95] avd name changed --- .forgejo/workflows/buildAndTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 7b587fb0..95eb2958 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -57,7 +57,7 @@ jobs: echo "⌛ Démarrage de l'émulateur en tâche de fond..." # 3. Lancement corrigé : exclusion de -gpu off (qui brise SwiftShader) et forçage d'un profil CPU standard - emulator -avd pixel_test \ + emulator -avd postit_test_avd \ -no-window \ -no-audio \ -no-boot-anim \ From 801839465ce90bf4d6bc841bd3cf28ed6cc3231d Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 05:25:18 +0100 Subject: [PATCH 33/95] =?UTF-8?q?donne=20l'acc=C3=A8s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .forgejo/workflows/buildAndTest.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 95eb2958..e9c3fe89 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -41,7 +41,10 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - + - name: Préparer les permissions KVM dans le conteneur + run: | + # Donne l'accès en écriture à tout le monde sur le périphérique KVM mappé + sudo chmod 666 /dev/kvm || true - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 From bc6861dd26d1cbca376a0d26b15f2e1a50792288 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 05:38:07 +0100 Subject: [PATCH 34/95] no kvm --- .forgejo/workflows/buildAndTest.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index e9c3fe89..60a40d7a 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -59,14 +59,10 @@ jobs: echo "⌛ Démarrage de l'émulateur en tâche de fond..." - # 3. Lancement corrigé : exclusion de -gpu off (qui brise SwiftShader) et forçage d'un profil CPU standard emulator -avd postit_test_avd \ -no-window \ -no-audio \ - -no-boot-anim \ - -gpu swiftshader \ - -no-metrics \ - -qemu -cpu max,fma=off,avx2=off,bmi1=off,bmi2=off & + -no-boot-anim & echo "Attente de la détection par ADB..." adb wait-for-device From af1ca7b1bb60f761c4f4220c3853bcd8b764a16f Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 05:44:46 +0100 Subject: [PATCH 35/95] for reading --- .forgejo/workflows/buildAndTest.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 60a40d7a..09a53f04 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -44,7 +44,8 @@ jobs: - name: Préparer les permissions KVM dans le conteneur run: | # Donne l'accès en écriture à tout le monde sur le périphérique KVM mappé - sudo chmod 666 /dev/kvm || true + chmod 666 /dev/kvm + - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 @@ -57,17 +58,20 @@ jobs: # 2. Initialisation obligatoire du serveur ADB adb start-server - echo "⌛ Démarrage de l'émulateur en tâche de fond..." + echo "🚀 Démarrage de l'émulateur en tâche de fond..." emulator -avd postit_test_avd \ -no-window \ -no-audio \ - -no-boot-anim & + -no-boot-anim \ + -gpu swiftshader \ + -no-metrics \ + -qemu -cpu max,fma=off,avx2=off,bmi1=off,bmi2=off & - echo "Attente de la détection par ADB..." + echo "⌛ Attente de la détection par ADB..." adb wait-for-device - echo "Attente de la fin du boot d'Android..." + echo "⌛ Attente de la fin du boot d'Android..." OUT="" COUNTER=0 # Sécurité : ajout d'un timeout (max 30 essais) pour éviter que la CI ne tourne à l'infini si le boot gèle @@ -86,11 +90,11 @@ jobs: - name: Test run: | - echo "⌛ Installation de l'APK de Debug..." + echo "🚀 Installation de l'APK de Debug..." # Ajustez le chemin vers l'APK généré par votre build de debug cd /src/_src APK=src/PostIt/PostIt.Android/bin/Debug/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk adb install ${APK} - echo "⌛ Lancement des tests..." + echo "🚀 Lancement des tests..." cd /src/_src && dotnet test --verbosity normal && echo "✅ Success !" From 4f9acb17ff16bcfa648a6f51a471a622185b6258 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:07:50 +0100 Subject: [PATCH 36/95] pour test --- .forgejo/workflows/buildAndTest.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 09a53f04..ab6c6975 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -28,7 +28,6 @@ jobs: runs-on: docker container: image: pazof/yavsc-build-env:debian12-dotnet10-android36-v5 - options: --device /dev/kvm --privileged steps: - name: Clone yavsc run: | From 41a1febb248072448ed091b6f626f540b7f090d0 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:09:16 +0100 Subject: [PATCH 37/95] pour test --- .forgejo/workflows/buildAndTest.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index ab6c6975..a5577a69 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -42,8 +42,7 @@ jobs: echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - name: Préparer les permissions KVM dans le conteneur run: | - # Donne l'accès en écriture à tout le monde sur le périphérique KVM mappé - chmod 666 /dev/kvm + ls -l /dev/kvm - name: Démarrer l'émulateur QEMU Headless run: | From 7d1e5763ff520b6e844a219def3687355d7ba963 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:19:42 +0100 Subject: [PATCH 38/95] cleanup --- .forgejo/workflows/buildAndTest.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index a5577a69..b49ab75b 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,9 +40,6 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: Préparer les permissions KVM dans le conteneur - run: | - ls -l /dev/kvm - name: Démarrer l'émulateur QEMU Headless run: | From 955725d7394a99bcb66a2117da68ed43018dcc5a Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:23:22 +0100 Subject: [PATCH 39/95] =?UTF-8?q?Diagnostic=20syst=C3=A8me=20absolu=20du?= =?UTF-8?q?=20Runner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .forgejo/workflows/buildAndTest.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index b49ab75b..3a9710bb 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,7 +40,16 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - + - name: Diagnostic système absolu du Runner + run: | + echo "=== Utilisateur actuel ===" + id + echo "=== Liste des périphériques visibles ===" + ls -la /dev/kvm 2>&1 || echo "❌ /dev/kvm INTROUVABLE" + echo "=== Groupes disponibles ===" + getent group || true + echo "=== Variables d'environnement du conteneur ===" + env - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 From e337e25aa45a0abd1c8a64930a86114ec6fd1e00 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:27:04 +0100 Subject: [PATCH 40/95] Forcer l'appropriation et les droits KVM --- .forgejo/workflows/buildAndTest.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 3a9710bb..6f656f93 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,16 +40,12 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: Diagnostic système absolu du Runner + - name: Forcer l'appropriation et les droits KVM pour Podman run: | - echo "=== Utilisateur actuel ===" - id - echo "=== Liste des périphériques visibles ===" - ls -la /dev/kvm 2>&1 || echo "❌ /dev/kvm INTROUVABLE" - echo "=== Groupes disponibles ===" - getent group || true - echo "=== Variables d'environnement du conteneur ===" - env + # 1. Supprime les restrictions d'ACL héritées de Podman Rootless + setfacl -b /dev/kvm || true + # 2. Force les droits en lecture/écriture totale pour tout le monde dans le conteneur + chmod 666 /dev/kvm - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 From 8978225539963681c767e0d9ccf44ce7e5f2ab60 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:39:22 +0100 Subject: [PATCH 41/95] ne rien forcer --- .forgejo/workflows/buildAndTest.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 6f656f93..a66018f9 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,12 +40,6 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: Forcer l'appropriation et les droits KVM pour Podman - run: | - # 1. Supprime les restrictions d'ACL héritées de Podman Rootless - setfacl -b /dev/kvm || true - # 2. Force les droits en lecture/écriture totale pour tout le monde dans le conteneur - chmod 666 /dev/kvm - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 From 016d16fda4f978251052e965c519813e37013962 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:41:57 +0100 Subject: [PATCH 42/95] Injecter le groupe KVM --- .forgejo/workflows/buildAndTest.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index a66018f9..35c117b6 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,6 +40,13 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" + - name: Injecter le groupe KVM pour l'émulateur Android + run: | + # 1. Vérifie si le groupe kvm existe déjà à l'intérieur, sinon le crée avec le GID 232 détecté par Podman + if ! getent group kvm > /dev/null; then + echo "kvm:x:232:root" >> /etc/group + echo "✅ Groupe KVM injecté artificiellement" + fi - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 From c338fb33ed7be55f4da1a8147535eb4b8c55a595 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:44:38 +0100 Subject: [PATCH 43/95] Injecte la ligne parfaite --- .forgejo/workflows/buildAndTest.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 35c117b6..6be303e4 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -42,11 +42,15 @@ jobs: echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - name: Injecter le groupe KVM pour l'émulateur Android run: | - # 1. Vérifie si le groupe kvm existe déjà à l'intérieur, sinon le crée avec le GID 232 détecté par Podman - if ! getent group kvm > /dev/null; then - echo "kvm:x:232:root" >> /etc/group - echo "✅ Groupe KVM injecté artificiellement" - fi + # 1. Nettoie toute ancienne ligne kvm pour éviter les doublons + sed -i '/^kvm:/d' /etc/group || true + + # 2. Injecte la ligne parfaite requise par la regex de l'émulateur Android + echo "kvm:x:232:root" >> /etc/group + + # 3. Vérification visuelle dans les logs de la CI + echo "=== Contenu validé pour l'émulateur ===" + grep kvm /etc/group - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 From bbd869ab88a5a2a4e84b935b4efb9111a6625abd Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:48:18 +0100 Subject: [PATCH 44/95] export USER=root --- .forgejo/workflows/buildAndTest.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 6be303e4..e7b5b66b 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -42,17 +42,16 @@ jobs: echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - name: Injecter le groupe KVM pour l'émulateur Android run: | - # 1. Nettoie toute ancienne ligne kvm pour éviter les doublons + # 1. Nettoie l'ancien résidu sed -i '/^kvm:/d' /etc/group || true - # 2. Injecte la ligne parfaite requise par la regex de l'émulateur Android + # 2. Injecte la correspondance exacte requise echo "kvm:x:232:root" >> /etc/group - - # 3. Vérification visuelle dans les logs de la CI - echo "=== Contenu validé pour l'émulateur ===" - grep kvm /etc/group - name: Démarrer l'émulateur QEMU Headless run: | + export USER=root + export USERNAME=root + # 1. Variables d'environnement pour stabiliser le noyau Android 36 export RE_EMULATOR_DISABLE_CPUID_CHECK=1 export ANDROID_EMULATOR_DISABLE_VULKAN=1 From 955d925f33ce439b6522c339e399a00bb3b489d2 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:53:28 +0100 Subject: [PATCH 45/95] -no-accel-check --- .forgejo/workflows/buildAndTest.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index e7b5b66b..59212de3 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,18 +40,8 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: Injecter le groupe KVM pour l'émulateur Android - run: | - # 1. Nettoie l'ancien résidu - sed -i '/^kvm:/d' /etc/group || true - - # 2. Injecte la correspondance exacte requise - echo "kvm:x:232:root" >> /etc/group - name: Démarrer l'émulateur QEMU Headless run: | - export USER=root - export USERNAME=root - # 1. Variables d'environnement pour stabiliser le noyau Android 36 export RE_EMULATOR_DISABLE_CPUID_CHECK=1 export ANDROID_EMULATOR_DISABLE_VULKAN=1 @@ -64,14 +54,7 @@ jobs: echo "🚀 Démarrage de l'émulateur en tâche de fond..." - emulator -avd postit_test_avd \ - -no-window \ - -no-audio \ - -no-boot-anim \ - -gpu swiftshader \ - -no-metrics \ - -qemu -cpu max,fma=off,avx2=off,bmi1=off,bmi2=off & - + emulator -avd postit_test_avd -no-window -no-audio -no-accel-check & echo "⌛ Attente de la détection par ADB..." adb wait-for-device From 7ccb23ba2836676c802f6ef212ccc7c64d4ccdac Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 06:58:47 +0100 Subject: [PATCH 46/95] try the direct call --- .forgejo/workflows/buildAndTest.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 59212de3..b1644627 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -46,15 +46,14 @@ jobs: export RE_EMULATOR_DISABLE_CPUID_CHECK=1 export ANDROID_EMULATOR_DISABLE_VULKAN=1 export ANDROID_EMULATOR_FORCE_GLES_2=1 - export ANDROID_HOME=/opt/android-sdk - export ANDROID_AVD_HOME=${ANDROID_HOME}/.android/avd + # 1. Variables de stabilisation + export RE_EMULATOR_DISABLE_CPUID_CHECK=1 + export ANDROID_EMULATOR_DISABLE_VULKAN=1 - # 2. Initialisation obligatoire du serveur ADB - adb start-server + # 2. Appel direct du moteur sans passer par le script de vérification de Google + # Remplacez "postit_test_avd" par le nom exact de votre AVD + /opt/android-sdk/emulator/qemu/linux-x86_64/qemu-system-x86_64 -avd postit_test_avd -no-window -no-audio & - echo "🚀 Démarrage de l'émulateur en tâche de fond..." - - emulator -avd postit_test_avd -no-window -no-audio -no-accel-check & echo "⌛ Attente de la détection par ADB..." adb wait-for-device From 7e3b0a72ade1e35ce2f46201c722b70a7658a0d6 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 07:03:03 +0100 Subject: [PATCH 47/95] LD_LIBRARY_PATH --- .forgejo/workflows/buildAndTest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index b1644627..b2c9263a 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -49,6 +49,7 @@ jobs: # 1. Variables de stabilisation export RE_EMULATOR_DISABLE_CPUID_CHECK=1 export ANDROID_EMULATOR_DISABLE_VULKAN=1 + export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:$LD_LIBRARY_PATH # 2. Appel direct du moteur sans passer par le script de vérification de Google # Remplacez "postit_test_avd" par le nom exact de votre AVD From c4f6913fdb1165ca4ff67cdeb81d2af01bd57f02 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 07:05:00 +0100 Subject: [PATCH 48/95] LD_LIBRARY_PATH 2 --- .forgejo/workflows/buildAndTest.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index b2c9263a..4fa26f61 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -49,7 +49,8 @@ jobs: # 1. Variables de stabilisation export RE_EMULATOR_DISABLE_CPUID_CHECK=1 export ANDROID_EMULATOR_DISABLE_VULKAN=1 - export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH + # 2. Appel direct du moteur sans passer par le script de vérification de Google # Remplacez "postit_test_avd" par le nom exact de votre AVD From 8f9c55bf2d3f1424ab89826f0fba576f3598be38 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 07:09:10 +0100 Subject: [PATCH 49/95] apt-get update && apt-get install --- .forgejo/workflows/buildAndTest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 4fa26f61..0bc21065 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -51,6 +51,7 @@ jobs: export ANDROID_EMULATOR_DISABLE_VULKAN=1 export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH + apt-get update && apt-get install -y libxi6 libxext6 libxrender1 libxtst6 # 2. Appel direct du moteur sans passer par le script de vérification de Google # Remplacez "postit_test_avd" par le nom exact de votre AVD From 800323915985c29c09f07b318b0ad4638b081d41 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 08:08:13 +0100 Subject: [PATCH 50/95] docker image version bump --- .forgejo/workflows/buildAndTest.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 0bc21065..5f5f62a5 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -27,7 +27,7 @@ jobs: build: runs-on: docker container: - image: pazof/yavsc-build-env:debian12-dotnet10-android36-v5 + image: pazof/yavsc-build-env:debian13-dotnet10-android36-v6 steps: - name: Clone yavsc run: | @@ -46,16 +46,10 @@ jobs: export RE_EMULATOR_DISABLE_CPUID_CHECK=1 export ANDROID_EMULATOR_DISABLE_VULKAN=1 export ANDROID_EMULATOR_FORCE_GLES_2=1 - # 1. Variables de stabilisation export RE_EMULATOR_DISABLE_CPUID_CHECK=1 export ANDROID_EMULATOR_DISABLE_VULKAN=1 - export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH - apt-get update && apt-get install -y libxi6 libxext6 libxrender1 libxtst6 - - # 2. Appel direct du moteur sans passer par le script de vérification de Google - # Remplacez "postit_test_avd" par le nom exact de votre AVD - /opt/android-sdk/emulator/qemu/linux-x86_64/qemu-system-x86_64 -avd postit_test_avd -no-window -no-audio & + emulator -avd postit_test_avd -no-window -no-audio & echo "⌛ Attente de la détection par ADB..." adb wait-for-device From f7956b045968b4f54d21a681dd993ed31e489a62 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 08:28:19 +0100 Subject: [PATCH 51/95] appel direct --- .forgejo/workflows/buildAndTest.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 5f5f62a5..629e516c 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -48,8 +48,9 @@ jobs: export ANDROID_EMULATOR_FORCE_GLES_2=1 export RE_EMULATOR_DISABLE_CPUID_CHECK=1 export ANDROID_EMULATOR_DISABLE_VULKAN=1 + export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH - emulator -avd postit_test_avd -no-window -no-audio & + /opt/android-sdk/emulator/qemu/linux-x86_64/qemu-system-x86_64 -avd postit_test_avd -no-window -no-audio & echo "⌛ Attente de la détection par ADB..." adb wait-for-device From 1ba114ee332d219b4f1ff9f6fa59dc5d4e94e7a7 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 08:44:44 +0100 Subject: [PATCH 52/95] gpu swiftshader --- .forgejo/workflows/buildAndTest.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 629e516c..b5169a1c 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -50,7 +50,13 @@ jobs: export ANDROID_EMULATOR_DISABLE_VULKAN=1 export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH - /opt/android-sdk/emulator/qemu/linux-x86_64/qemu-system-x86_64 -avd postit_test_avd -no-window -no-audio & + emulator -avd postit_test_avd \ + -no-window \ + -no-audio \ + -no-boot-anim \ + -gpu swiftshader \ + -no-metrics \ + -timezone Europe/Paris & echo "⌛ Attente de la détection par ADB..." adb wait-for-device From eb0cf11703d5d43b5342caf1f1c5155660863e77 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 08:50:35 +0100 Subject: [PATCH 53/95] -cpu host --- .forgejo/workflows/buildAndTest.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index b5169a1c..050ac917 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -56,7 +56,8 @@ jobs: -no-boot-anim \ -gpu swiftshader \ -no-metrics \ - -timezone Europe/Paris & + -cores 2 \ + -qemu -cpu host & echo "⌛ Attente de la détection par ADB..." adb wait-for-device From 696117ac5a53acd1c18cc6ba55b5656e615a2922 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 08:56:14 +0100 Subject: [PATCH 54/95] another try --- .forgejo/workflows/buildAndTest.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 050ac917..5dd369e0 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -44,12 +44,12 @@ jobs: run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 export RE_EMULATOR_DISABLE_CPUID_CHECK=1 - export ANDROID_EMULATOR_DISABLE_VULKAN=1 - export ANDROID_EMULATOR_FORCE_GLES_2=1 - export RE_EMULATOR_DISABLE_CPUID_CHECK=1 - export ANDROID_EMULATOR_DISABLE_VULKAN=1 - export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH + export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH + export QT_QPA_PLATFORM=offscreen + export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1 + + # 2. Démarrer l'émulateur QEMU Headless emulator -avd postit_test_avd \ -no-window \ -no-audio \ From 1e3239498c51a1cbe20f71fd05167f57aacdfccc Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 08:58:46 +0100 Subject: [PATCH 55/95] -gpu software --- .forgejo/workflows/buildAndTest.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 5dd369e0..62c21784 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -54,10 +54,11 @@ jobs: -no-window \ -no-audio \ -no-boot-anim \ - -gpu swiftshader \ + -gpu software \ -no-metrics \ -cores 2 \ - -qemu -cpu host & + -memory 2048 \ + -qemu -cpu host,vme=off & echo "⌛ Attente de la détection par ADB..." adb wait-for-device From f285a168bd1c3a72bee5ae38cca910b281e52140 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 09:18:38 +0100 Subject: [PATCH 56/95] settings --- .forgejo/workflows/buildAndTest.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 62c21784..092dea0e 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,6 +40,8 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" + - name: check kvm + run: /sbin/kvm-ok - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 @@ -48,6 +50,19 @@ jobs: export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORM=offscreen export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1 + export ANDROID_SDK_ROOT=/opt/android-sdk + + echo "hw.ramSize = 3072" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "hw.ramSize = 2048" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "hw.heapSize = 256" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "hw.gpu.enabled = yes" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "hw.gpu.mode = host" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "Vulkan = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini + echo "enableHostVulkan = no" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini + +GLDirectMem = off +" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + # 2. Démarrer l'émulateur QEMU Headless emulator -avd postit_test_avd \ From e266c1de6eab4f250c01f15ea822e10516a7b445 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 09:20:36 +0100 Subject: [PATCH 57/95] syntax --- .forgejo/workflows/buildAndTest.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 092dea0e..3556c655 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -59,9 +59,7 @@ jobs: echo "hw.gpu.mode = host" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini echo "Vulkan = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini echo "enableHostVulkan = no" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini - -GLDirectMem = off -" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "GLDirectMem = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini # 2. Démarrer l'émulateur QEMU Headless From d6b45024dc7a8353ab664ca9998c90f7b0fb73d8 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 09:21:44 +0100 Subject: [PATCH 58/95] kvm-ok is to install (or not?) --- .forgejo/workflows/buildAndTest.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 3556c655..c20763d6 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,8 +40,7 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: check kvm - run: /sbin/kvm-ok + - name: Démarrer l'émulateur QEMU Headless run: | # 1. Variables d'environnement pour stabiliser le noyau Android 36 From 39400baadb68e2ac3b96e995af4cb77cd5eb5f29 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 09:25:23 +0100 Subject: [PATCH 59/95] giving up --- .forgejo/workflows/buildAndTest.yml | 51 ----------------------------- 1 file changed, 51 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index c20763d6..11eb196e 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -41,57 +41,6 @@ jobs: git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: Démarrer l'émulateur QEMU Headless - run: | - # 1. Variables d'environnement pour stabiliser le noyau Android 36 - export RE_EMULATOR_DISABLE_CPUID_CHECK=1 - - export LD_LIBRARY_PATH=/opt/android-sdk/emulator/lib64:/opt/android-sdk/emulator/lib64/qt/lib:$LD_LIBRARY_PATH - export QT_QPA_PLATFORM=offscreen - export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1 - export ANDROID_SDK_ROOT=/opt/android-sdk - - echo "hw.ramSize = 3072" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "hw.ramSize = 2048" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "hw.heapSize = 256" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "hw.gpu.enabled = yes" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "hw.gpu.mode = host" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "Vulkan = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini - echo "enableHostVulkan = no" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini - echo "GLDirectMem = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini - - - # 2. Démarrer l'émulateur QEMU Headless - emulator -avd postit_test_avd \ - -no-window \ - -no-audio \ - -no-boot-anim \ - -gpu software \ - -no-metrics \ - -cores 2 \ - -memory 2048 \ - -qemu -cpu host,vme=off & - - echo "⌛ Attente de la détection par ADB..." - adb wait-for-device - - echo "⌛ Attente de la fin du boot d'Android..." - OUT="" - COUNTER=0 - # Sécurité : ajout d'un timeout (max 30 essais) pour éviter que la CI ne tourne à l'infini si le boot gèle - while [[ "$OUT" != "1" && $COUNTER -lt 30 ]]; do - sleep 10 - OUT=$(adb shell getprop sys.boot_completed | tr -d '\r') - echo "Statut actuel : ${OUT:-0}" - let COUNTER=COUNTER+1 - done - - if [ "$OUT" != "1" ]; then - echo "❌ Échec du boot d'Android après délai imparti." - exit 1 - fi - echo "✅ L'émulateur Android est prêt !" - - name: Test run: | echo "🚀 Installation de l'APK de Debug..." From b0e70eb1989d2279a627cdf1c5f83ccb264d1bd0 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 17:06:14 +0100 Subject: [PATCH 60/95] give a try to the v7 --- .forgejo/workflows/buildAndTest.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 11eb196e..c5797c6c 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -27,7 +27,7 @@ jobs: build: runs-on: docker container: - image: pazof/yavsc-build-env:debian13-dotnet10-android36-v6 + image: pazof/yavsc-build-env:debian13-dotnet10-android36-v7 steps: - name: Clone yavsc run: | @@ -40,6 +40,20 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" + - name: setup avd qemu + run: | + export QT_QPA_PLATFORM=offscreen + export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1 + export ANDROID_SDK_ROOT=/opt/android-sdk + + echo "hw.ramSize = 3072" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "hw.ramSize = 2048" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "hw.heapSize = 256" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "hw.gpu.enabled = yes" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "hw.gpu.mode = host" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini + echo "Vulkan = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini + echo "enableHostVulkan = no" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini + echo "GLDirectMem = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini - name: Test run: | From 99c500d177cfb4e21528f8f992bc9b085a3a8e8a Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 17:13:20 +0100 Subject: [PATCH 61/95] start qemu --- .forgejo/workflows/buildAndTest.yml | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index c5797c6c..ccdd0a6c 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -55,6 +55,39 @@ jobs: echo "enableHostVulkan = no" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini echo "GLDirectMem = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini + - name: Start qemu avd + run: | + # 2. Démarrer l'émulateur QEMU Headless + emulator -avd postit_test_avd \ + -no-window \ + -no-audio \ + -no-boot-anim \ + -no-metrics \ + -cores 2 \ + -memory 2048 & + + echo "⌛ Attente de la détection par ADB..." + adb wait-for-device + + echo "⌛ Attente de la fin du boot d'Android..." + OUT="" + + COUNTER=0 + # Sécurité : ajout d'un timeout (max 30 essais) pour éviter + { que la CI ne tourne à l'infini si le boot gèle + while [[ "$OUT" != "1" && $COUNTER -lt 30 ]]; do + sleep 10 + OUT=$(adb shell getprop sys.boot_completed | tr -d '\r') + echo "Statut actuel : ${OUT:-0}" + let COUNTER=COUNTER+1 + done + + if [ "$OUT" != "1" ]; then + echo "❌ Échec du boot d'Android après délai imparti." + exit 1 + fi + echo "✅ L'émulateur Android est prêt !" + - name: Test run: | echo "🚀 Installation de l'APK de Debug..." From d63f065e035a8d6c8270f0cbcfa755bd78d9a306 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 17:15:11 +0100 Subject: [PATCH 62/95] try host cpu --- .forgejo/workflows/buildAndTest.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index ccdd0a6c..815bab07 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -64,7 +64,8 @@ jobs: -no-boot-anim \ -no-metrics \ -cores 2 \ - -memory 2048 & + -memory 2048 \ + -qemu -cpu host,vme=off & echo "⌛ Attente de la détection par ADB..." adb wait-for-device From 98cab22e60e32b8e69297ce14494d2907d1209bd Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 17:16:47 +0100 Subject: [PATCH 63/95] RE_EMULATOR_DISABLE_CPUID_CHECK --- .forgejo/workflows/buildAndTest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 815bab07..3de2adf2 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -45,6 +45,7 @@ jobs: export QT_QPA_PLATFORM=offscreen export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1 export ANDROID_SDK_ROOT=/opt/android-sdk + export RE_EMULATOR_DISABLE_CPUID_CHECK=1 echo "hw.ramSize = 3072" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini echo "hw.ramSize = 2048" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini From 29d842a8f94d3fdb8798f843d7aa2f4c2a538f43 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 17:18:31 +0100 Subject: [PATCH 64/95] -cores 1 --- .forgejo/workflows/buildAndTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 3de2adf2..b6694c2c 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -64,7 +64,7 @@ jobs: -no-audio \ -no-boot-anim \ -no-metrics \ - -cores 2 \ + -cores 1 \ -memory 2048 \ -qemu -cpu host,vme=off & From 366cd0eed93f2609ae0b0f79362f8edb9619a182 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 17:22:03 +0100 Subject: [PATCH 65/95] try with vulkan enabled --- .forgejo/workflows/buildAndTest.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index b6694c2c..c7347135 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -52,9 +52,6 @@ jobs: echo "hw.heapSize = 256" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini echo "hw.gpu.enabled = yes" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini echo "hw.gpu.mode = host" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "Vulkan = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini - echo "enableHostVulkan = no" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini - echo "GLDirectMem = off" >> ${ANDROID_SDK_ROOT}/.android/avd/advancedFeatures.ini - name: Start qemu avd run: | From bfcb7cd2efa396dd61729faacbd94611487e5d3d Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 19:23:27 +0100 Subject: [PATCH 66/95] disable the Xamarin.UITest --- .forgejo/workflows/buildAndTest.yml | 46 ------------------- .../PostIt.Tests/AndroidAppLaunchTests.cs | 3 +- 2 files changed, 2 insertions(+), 47 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index c7347135..7e0bc2b0 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -40,52 +40,6 @@ jobs: fi git submodule update --init --recursive echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: setup avd qemu - run: | - export QT_QPA_PLATFORM=offscreen - export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1 - export ANDROID_SDK_ROOT=/opt/android-sdk - export RE_EMULATOR_DISABLE_CPUID_CHECK=1 - - echo "hw.ramSize = 3072" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "hw.ramSize = 2048" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "hw.heapSize = 256" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "hw.gpu.enabled = yes" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - echo "hw.gpu.mode = host" >> ${ANDROID_SDK_ROOT}/.android/avd/postit_test_avd.ini - - - name: Start qemu avd - run: | - # 2. Démarrer l'émulateur QEMU Headless - emulator -avd postit_test_avd \ - -no-window \ - -no-audio \ - -no-boot-anim \ - -no-metrics \ - -cores 1 \ - -memory 2048 \ - -qemu -cpu host,vme=off & - - echo "⌛ Attente de la détection par ADB..." - adb wait-for-device - - echo "⌛ Attente de la fin du boot d'Android..." - OUT="" - - COUNTER=0 - # Sécurité : ajout d'un timeout (max 30 essais) pour éviter - { que la CI ne tourne à l'infini si le boot gèle - while [[ "$OUT" != "1" && $COUNTER -lt 30 ]]; do - sleep 10 - OUT=$(adb shell getprop sys.boot_completed | tr -d '\r') - echo "Statut actuel : ${OUT:-0}" - let COUNTER=COUNTER+1 - done - - if [ "$OUT" != "1" ]; then - echo "❌ Échec du boot d'Android après délai imparti." - exit 1 - fi - echo "✅ L'émulateur Android est prêt !" - name: Test run: | diff --git a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs index 8a256afc..d4980d26 100644 --- a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs +++ b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs @@ -23,7 +23,8 @@ public class AndroidAppLaunchTests _output = output; } - [Fact] + // https://twosixtech.com/blog/integrating-docker-and-adb/ + // FIXME ala hosted shared resource adb server - [Fact] public void PostIt_starts_and_draws_a_first_frame_on_the_emulator() { if (!IsPackageInstalledOnAnyDevice()) From 45299d14c1f8cf1ec7073a4d94525ca75ee3398a Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 19:27:43 +0100 Subject: [PATCH 67/95] no apk to install --- .forgejo/workflows/buildAndTest.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 7e0bc2b0..c29b844a 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -43,11 +43,5 @@ jobs: - name: Test run: | - echo "🚀 Installation de l'APK de Debug..." - # Ajustez le chemin vers l'APK généré par votre build de debug - cd /src/_src - APK=src/PostIt/PostIt.Android/bin/Debug/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk - adb install ${APK} - echo "🚀 Lancement des tests..." cd /src/_src && dotnet test --verbosity normal && echo "✅ Success !" From 5d81ab3094514d116603e4af241cbe64660d0dd2 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 19:43:47 +0100 Subject: [PATCH 68/95] Release 1.0.8-rc3 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bec19e53..6adf462a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,8 @@ pour la production des paquets `.deb`. ### Added -* a code cleanup -* a first Xamarin.UITest is successful +* a code cleanup, +* a first Xamarin.UITest is successful, but disabled, because breaking the actual CI process, * Android app starts, the login process succeeds ### Changed From 400db949531ede4014a5c4922e1903b090394299 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 19:49:42 +0100 Subject: [PATCH 69/95] hit the runner --- .forgejo/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index a4f86b37..00e91a41 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -52,7 +52,7 @@ jobs: release: runs-on: docker container: - image: pazof/yavsc-build-env:debian12-dotnet10-android36-v5 + image: pazof/yavsc-build-env:debian12-dotnet10-android36-v7 steps: - name: Clone du repo au tag demandé env: From 912dd88a028f0064afce0d8d36dedc2394f07383 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 19:52:41 +0100 Subject: [PATCH 70/95] gives this release a title --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6adf462a..2c1cefde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Cette convention est partagée avec le dépôt [`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) pour la production des paquets `.deb`. -## [1.0.8-rc3] - unstable +## [1.0.8-rc3] Client Alpha Android - unstable ### Added From 09c8d7971425fc8fb838c174de864955a044a57e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 20:04:39 +0100 Subject: [PATCH 71/95] debian13! --- .forgejo/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 00e91a41..d6fc2e61 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -52,7 +52,7 @@ jobs: release: runs-on: docker container: - image: pazof/yavsc-build-env:debian12-dotnet10-android36-v7 + image: pazof/yavsc-build-env:debian13-dotnet10-android36-v7 steps: - name: Clone du repo au tag demandé env: From ad2285b8c541b2e8a3bb988e80b2c1eef356ac5e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 20:19:51 +0100 Subject: [PATCH 72/95] was breakingthe format --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c1cefde..6adf462a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Cette convention est partagée avec le dépôt [`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) pour la production des paquets `.deb`. -## [1.0.8-rc3] Client Alpha Android - unstable +## [1.0.8-rc3] - unstable ### Added From 671afebc12ceea66fc0c9c7cbbb75a5387c75fe0 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 22:26:14 +0100 Subject: [PATCH 73/95] Build image version bump --- .forgejo/workflows/buildAndTest.yml | 2 +- .forgejo/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index c29b844a..86694c89 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -27,7 +27,7 @@ jobs: build: runs-on: docker container: - image: pazof/yavsc-build-env:debian13-dotnet10-android36-v7 + image: pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v1 steps: - name: Clone yavsc run: | diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index d6fc2e61..930c97b9 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -52,7 +52,7 @@ jobs: release: runs-on: docker container: - image: pazof/yavsc-build-env:debian13-dotnet10-android36-v7 + image: pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v1 steps: - name: Clone du repo au tag demandé env: From 0034a311f279ecf3246305e854d324de4f183e24 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 23:08:23 +0100 Subject: [PATCH 74/95] chore(release): bump version via gitversion for 1.0.8-rc4 --- src/PostIt/PostIt.Android/PostIt.Android.csproj | 13 ++++++------- src/PostIt/PostIt.Browser/PostIt.Browser.csproj | 4 ++-- src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj | 4 ++-- src/PostIt/PostIt.Tests/PostIt.Tests.csproj | 9 +++------ src/PostIt/PostIt/PostIt.csproj | 11 ++++++----- src/Yavsc.Abstract/Yavsc.Abstract.csproj | 4 ++-- src/Yavsc.Api.Client/Yavsc.Api.Client.csproj | 4 ++-- src/Yavsc.Api/Yavsc.Api.csproj | 4 ++-- src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj | 4 ++-- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 4 ++-- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 4 ++-- src/Yavsc.Org/Yavsc.Org.csproj | 4 ++-- src/Yavsc.Server/Yavsc.Server.csproj | 4 ++-- src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj | 4 ++-- src/cli/cli.csproj | 2 +- 15 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index 62b494f8..7f44645d 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -9,23 +9,22 @@ 1.0 apk false + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1 - Resources\drawable\Icon.png - - - - + - - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index 2816b2f8..67600a72 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -6,7 +6,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -15,4 +15,4 @@ - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index e47c5b6d..30ba65c4 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -7,7 +7,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -24,4 +24,4 @@ - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/PostIt.Tests.csproj b/src/PostIt/PostIt.Tests/PostIt.Tests.csproj index 663bdd12..86a7998a 100644 --- a/src/PostIt/PostIt.Tests/PostIt.Tests.csproj +++ b/src/PostIt/PostIt.Tests/PostIt.Tests.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -16,9 +16,7 @@ - - @@ -28,6 +26,5 @@ - - - + + \ No newline at end of file diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index bde8fd41..1ae36a44 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -1,14 +1,16 @@ - + net10.0 enable latest + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1 - - PreserveNewest @@ -31,9 +33,8 @@ - - + \ No newline at end of file diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index ebbb2d1e..5094b14d 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -11,10 +11,10 @@ latest 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 - + \ No newline at end of file diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index 047223a6..4c560f23 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -17,7 +17,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -26,4 +26,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index 2ddeb72d..3655079b 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -17,4 +17,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index 34536806..bc4e34ba 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -35,4 +35,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 019fb102..8692b28a 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -18,4 +18,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 34de5bd8..8019a78f 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -11,7 +11,7 @@ $(MSBuildProjectDirectory)\test.runsettings 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -89,4 +89,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index aeb94a8e..6dbceb4a 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -9,7 +9,7 @@ https://github.com/pazof/yavsc 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -54,4 +54,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index 7adb3a78..839e476a 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -43,4 +43,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index 7f2f382a..7bd8f24a 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -14,7 +14,7 @@ --> 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -23,4 +23,4 @@ - + \ No newline at end of file diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index 237ccdd8..49aa64b1 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 From e62adedc1782df80b387bb0823a261a69cc26b58 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 20:10:32 +0100 Subject: [PATCH 75/95] Fixes ACL backend support --- .vscode/tasks.json | 32 +++---- CHANGELOG.md | 10 +++ src/Yavsc.Abstract/Constants.cs | 7 ++ src/Yavsc.Blogs.Tests/BlogAclApiTests.cs | 90 ++++++++++++++++++- src/Yavsc.Blogs.Tests/BlogApiTests.cs | 21 +---- .../BlogsWebServerFixture.cs | 43 ++++++++- .../Controllers/BlogApiController.cs | 17 ++-- .../Controllers/BlogTagsApiController.cs | 2 +- .../Controllers/CircleApiController.cs | 2 +- .../Controllers/CommentsApiController.cs | 2 +- src/Yavsc.Server/Helpers/PayloadHelpers.cs | 21 +++++ src/Yavsc.Server/Models/Blog/BlogPost.cs | 3 +- src/Yavsc.Server/Models/Blog/BlogTag.cs | 3 + src/Yavsc.Server/Models/Blog/Comment.cs | 8 +- 14 files changed, 205 insertions(+), 56 deletions(-) create mode 100644 src/Yavsc.Server/Helpers/PayloadHelpers.cs diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3c0c0bf2..3faedae0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,5 +1,21 @@ { "version": "2.0.0", + "isRoot": true, + "problemMatcher": [ + { + "owner": "dotnet", + "fileLocation": ["relative", "${workspaceFolder}"], + "source": "dotnet", + "pattern": { + "regexp": "^\\s+(.*)\\((\\d+):(\\d+)\\):\\s+(error|warning)\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ], "tasks": [ { "label": "run-debug-android", @@ -20,21 +36,6 @@ "-p:AndroidAttachDebugger=true", "-p:AndroidSdbHostPort=55555", "-p:AndroidSdbTargetPort=55555" - ], - "problemMatcher": [ - { - "owner": "dotnet", - "fileLocation": ["relative", "${workspaceFolder}"], - "source": "dotnet", - "pattern": { - "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*)$", - "file": 1, - "line": 2, - "column": 3, - "severity": 4, - "message": 5 - } - } ] }, { @@ -45,7 +46,6 @@ "group": "build", "isBuildCommand": true, "isTestCommand": false, - "problemMatcher": ["$msCompile"], "isBackground": true }, { diff --git a/CHANGELOG.md b/CHANGELOG.md index 6adf462a..8425c2c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,16 @@ Cette convention est partagée avec le dépôt [`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) pour la production des paquets `.deb`. +## [1.0.8-rc4] - unstable + +### Added + +### Changed + +### Fixed + +* [TODO] bug loading a blog post from PostIt, ACL come along with and don't need any "Refresh" button. + ## [1.0.8-rc3] - unstable ### Added diff --git a/src/Yavsc.Abstract/Constants.cs b/src/Yavsc.Abstract/Constants.cs index 78ff0838..a2649491 100644 --- a/src/Yavsc.Abstract/Constants.cs +++ b/src/Yavsc.Abstract/Constants.cs @@ -7,6 +7,13 @@ namespace Yavsc { public const string APIPrefix = "api/v1"; + + public const string BlogSpotPath = "blogspot"; + public const string BlogAclPath = "blogacl"; + public const string BlogTagPath = "blogtag"; + public const string CirclePath = "circle"; + public const string CommentsPath = "blogcomments"; + public static readonly Scope[] SiteScopes = { new Scope { Id = "profile", Description = "Your profile informations" }, new Scope { Id = "book" , Description ="Your booking interface"}, diff --git a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs index 92dd3b2c..c140c290 100644 --- a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs @@ -1,10 +1,12 @@ using System.Net; using System.Net.Http.Json; +using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Yavsc.Abstract.BlogSpot; using Yavsc.Models; using Yavsc.Models.Access; +using Yavsc.Models.Blog; using Yavsc.Tests.Shared; using static Yavsc.Constants; @@ -38,15 +40,16 @@ public sealed class BlogAclApiTests : IClassFixture { private readonly BlogsWebServerFixture _fixture; - public BlogAclApiTests(BlogsWebServerFixture fixture) { _fixture = fixture; } + private string BlogUrl() + => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogSpotPath}"; private string BlogAclUrl() - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/blogacl"; + => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogAclPath}"; /// Delete any ACL rows tied to the fixture's seeded /// (CircleId, BlogPostId) pair. The shared SQLite store @@ -120,7 +123,7 @@ public sealed class BlogAclApiTests : IClassFixture // owned by the caller. We seed the same shape pre-POST so the // test reproduces the prod scenario end-to-end. CleanupAcl(); - using var http = NewClient("alice"); + using var http = NewClient(_fixture.DefaultUserLogin); var payload = new PostAccessControlRulePayload { @@ -178,7 +181,7 @@ public sealed class BlogAclApiTests : IClassFixture [MemberData(nameof(BlogAclPayloadsForNever500))] public async Task PostCircleAuthorization_never_returns_500(PostAccessControlRulePayload payload) { - using var http = NewClient("alice"); + using var http = NewClient(_fixture.DefaultUserLogin); var response = await http.PostAsJsonAsync( BlogAclUrl(), payload, @@ -216,4 +219,83 @@ public sealed class BlogAclApiTests : IClassFixture ); } + + [Fact] + public async Task PostBlog_with_ACL_creates_a_post_and_Get_returns_it_in_the_list() + { + CleanupAcl(); + _fixture.SeedUser(_fixture.DefaultUserLogin); + _fixture.SeedUser("tester"); + _fixture.SeedCircle(_fixture.DefaultUserLogin, "test", + false, + new String[] + { + _fixture.DefaultUserLogin, + "tester" + }); + using var http = NewClient(_fixture.DefaultUserLogin ); + + // Create a minimal BlogPost. The server assigns Id, so we + // send 0 + an explicit AuthorId; the production + // BlogSpotService.Create() tolerates that. + var draft = new BlogPost + { + Id = 0, + Title = "Premier billet", + AuthorId = "tester", + Article = "Contenu de test.", + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow, + ACL = new List( + new CircleAuthorizationToBlogPost[] + { + new CircleAuthorizationToBlogPost + { + CircleId = _fixture.CircleId, + BlogPostId = _fixture.PostId + } + } + ) + }; + + var postResponse = await http.PostAsJsonAsync( + BlogUrl(), + draft, + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); + + // The POST returns the server-issued post (with a real Id). + var created = await postResponse.Content.ReadFromJsonAsync( + TestContext.Current.CancellationToken + ); + Assert.NotNull(created); + Assert.NotEqual(0, created!.Id); + Assert.Equal(draft.Title, created.Title); + + // The list should now contain exactly one entry. + var listResponse = await http.GetAsync( + BlogUrl(), + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); + + using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync( + TestContext.Current.CancellationToken + )); + Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); + Assert.Equal(2, doc.RootElement.GetArrayLength()); + Assert.Equal(created.Id, doc.RootElement[0].GetProperty("id").GetInt64()); + + // detail should return the same post, with ACL and tags. + var detailResponse = await http.GetAsync( + $"{BlogUrl()}/{created.Id}", + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode); + using var detailDoc = JsonDocument.Parse(await detailResponse.Content.ReadAsStringAsync( + TestContext.Current.CancellationToken + )); + Assert.Equal(JsonValueKind.Object, detailDoc.RootElement.ValueKind); + Assert.Equal(created.Id, detailDoc.RootElement.GetProperty("id").GetInt64()); + Assert.Equal(1, detailDoc.RootElement.GetProperty("acl").GetArrayLength()); + } + } diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs index 5a218316..e438e0b8 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs @@ -2,7 +2,6 @@ using System.Net; using System.Net.Http.Json; using System.Security.Claims; using System.Text.Json; -using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Server.Helpers; @@ -31,18 +30,6 @@ public sealed class BlogApiTests : IClassFixture _fixture = fixture; } - /// Reset the in-memory database to a known empty state. - /// UseInMemoryDatabase shares its store across the - /// lifetime of the instance, - /// so without a per-test reset the test order would leak - /// state between tests. - private void ResetDatabase() - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - } /// Reset the database and seed the /// tester row. Required @@ -55,7 +42,7 @@ public sealed class BlogApiTests : IClassFixture /// at SaveChanges and the controller returns 500. private void ResetAndSeedDefaultUser() { - ResetDatabase(); + _fixture.ResetDatabase(); _fixture.SeedUser("tester"); } @@ -111,7 +98,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task GetBlogs_returns_200_with_empty_list_when_no_posts() { - ResetDatabase(); + _fixture.ResetDatabase(); using var http = NewClient(); var response = await http.GetAsync("/api/v1/blog", @@ -266,7 +253,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task GetBlog_returns_401_when_no_token_is_provided() { - ResetDatabase(); + _fixture.ResetDatabase(); using var http = NewAnonymousClient(); // No Authorization header → the JwtBearer middleware @@ -443,7 +430,7 @@ public sealed class BlogApiTests : IClassFixture // behaviour so a future change that, say, makes Title // nullable in the model or drops [Required], triggers a // conscious update of the test (and probably of the VM). - ResetDatabase(); + _fixture.ResetDatabase(); using var http = NewClient(subject: "tester"); var draft = new BlogPost diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs index 218904ef..695af4c5 100644 --- a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs @@ -61,6 +61,7 @@ public sealed class BlogsWebServerFixture : WebHostFixture public long CircleId { get; private set; } public long PostId { get; private set; } + public string DefaultUserLogin { get => "alice"; } // A single SqliteConnection held open at the static level, // mirroring how Yavsc.Org.Tests.WebServerFixture hoists its @@ -169,7 +170,8 @@ public sealed class BlogsWebServerFixture : WebHostFixture // PermissionHandler ownership check sees a null // user id and rejects every PUT. options.MapInboundClaims = false; - options.TokenValidationParameters = new TokenValidationParameters + options.TokenValidationParameters + = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = TestTokenIssuer.Issuer, @@ -263,6 +265,27 @@ public sealed class BlogsWebServerFixture : WebHostFixture await Task.CompletedTask; return app; } +/// Reset the in-memory database to a known empty state. + /// UseInMemoryDatabase shares its store across the + /// lifetime of the instance, + /// so without a per-test reset the test order would leak + /// state between tests. + public void ResetDatabase() + { + using var scope = Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); + } + public void CleanupAcl() + { + using var scope = Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.CircleAuthorizationToBlogPost + .Where(a => a.CircleId == CircleId + && a.BlogPostId == PostId) + .ExecuteDelete(); + } public override void Dispose() { @@ -310,7 +333,8 @@ public sealed class BlogsWebServerFixture : WebHostFixture /// Optional hook to fill in fields /// like FullName / Avatar / EmailConfirmed /// that downstream tests assert on. - public ApplicationUser SeedUser(string userName, Action? configure = null) + public ApplicationUser SeedUser(string userName, + Action? configure = null) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); @@ -351,20 +375,31 @@ public sealed class BlogsWebServerFixture : WebHostFixture /// Create a circle owned by /// directly in the SQLite store and return its server-assigned /// id. - private long SeedCircle(string ownerId, string name, bool isPublic = false) + public long SeedCircle(string ownerId, string name, bool isPublic = false, + ICollection members = null + ) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var circle = new Circle { OwnerId = ownerId, Name = name, Public = isPublic }; db.Circle.Add(circle); db.SaveChanges(); + if (members != null && members.Count > 0) + { + foreach (String memberId in members) + { + var member = new CircleMember { CircleId = circle.Id, MemberId = memberId }; + db.CircleMembers.Add(member); + } + db.SaveChanges(); + } return circle.Id; } /// Create a blog post owned by /// directly in the SQLite store and return its server-assigned /// id. - private long SeedBlogPost(string authorId, string title) + public long SeedBlogPost(string authorId, string title) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs index fcd3a336..8c76f7ee 100644 --- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs @@ -9,7 +9,7 @@ namespace Yavsc.Blogs.Controllers { [Authorize("BlogScope")] [Produces("application/json")] - [Route(APIPrefix + "/blog")] + [Route(APIPrefix + "/" + BlogSpotPath)] public class BlogApiController : Controller { private readonly BlogSpotService blogSpotService; @@ -19,14 +19,14 @@ namespace Yavsc.Blogs.Controllers this.blogSpotService = blogSpotService; } - // GET: api/BlogApi + // GET: api/v1/blogspot [HttpGet] public async Task> GetBlogspot(int start = 0, int take = 25) { return await blogSpotService.Index(User, null, start, take); } - // GET: api/BlogApi/5 + // GET: api/v1/blogspot/5 [HttpGet("{id}", Name = "GetBlog")] public async Task GetBlog([FromRoute] long id) { @@ -43,7 +43,7 @@ namespace Yavsc.Blogs.Controllers return NotFound(); } - return Ok(blog); + return Ok(blog.GetPayload()); } catch (AuthorizationFailureException) { @@ -51,7 +51,7 @@ namespace Yavsc.Blogs.Controllers } } - // PUT: api/BlogApi/5 + // PUT: api/v1/blogspot/5 [HttpPut("{id}")] public async Task PutBlog(long id, [FromBody] Models.Blog.BlogPost blog) { @@ -83,7 +83,7 @@ namespace Yavsc.Blogs.Controllers return new StatusCodeResult(StatusCodes.Status204NoContent); } - // POST: api/v1/blog + // POST: api/v1/blogspot [HttpPost] public IActionResult PostBlog([FromBody] Models.Blog.BlogPost blog) { @@ -116,7 +116,8 @@ namespace Yavsc.Blogs.Controllers : (IFormFileCollection)new FormFileCollection(); var uid = User.GetUserId(); var post = blogSpotService.Create(uid, blog, files); - return CreatedAtRoute("GetBlog", new { id = post.Id }, post); + return CreatedAtRoute("GetBlog", new { id = post.Id }, + post.GetPayload()); } // DELETE: api/BlogApi/5 @@ -135,7 +136,7 @@ namespace Yavsc.Blogs.Controllers } await blogSpotService.Delete(User, id); - return Ok(blog); + return Ok(blog.GetPayload()); } /// diff --git a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs index ad6a0893..533e5594 100644 --- a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs @@ -6,7 +6,7 @@ using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix + "/blogtags")] + [Route(APIPrefix + "/" + BlogTagPath )] public class BlogTagsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Controllers/CircleApiController.cs b/src/Yavsc.Blogs/Controllers/CircleApiController.cs index 524f0471..fafd00ac 100644 --- a/src/Yavsc.Blogs/Controllers/CircleApiController.cs +++ b/src/Yavsc.Blogs/Controllers/CircleApiController.cs @@ -8,7 +8,7 @@ using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix +"/circle")] + [Route(APIPrefix +"/" + CirclePath)] public class CircleApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs index d4c80f99..d0747c29 100644 --- a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs @@ -11,7 +11,7 @@ namespace Yavsc.Blogs.Controllers { [Authorize] [Produces("application/json")] - [Route(APIPrefix + "/blogcomments")] + [Route(APIPrefix + "/" + CommentsPath)] public class CommentsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Server/Helpers/PayloadHelpers.cs b/src/Yavsc.Server/Helpers/PayloadHelpers.cs new file mode 100644 index 00000000..293cab02 --- /dev/null +++ b/src/Yavsc.Server/Helpers/PayloadHelpers.cs @@ -0,0 +1,21 @@ +using Yavsc.Models.Blog; + +public static class PayloadHelpers +{ + public static object GetPayload(this BlogPost post) + { + return new + { + post.Id, + post.Title, + post.Article, + post.DateCreated, + post.UserCreated, + post.DateModified, + post.UserModified, + post.AuthorId, + ACL = post.GetACL(), + Tags = post.GetTags() + }; + } +} \ No newline at end of file diff --git a/src/Yavsc.Server/Models/Blog/BlogPost.cs b/src/Yavsc.Server/Models/Blog/BlogPost.cs index 5a2dc58d..e5fd615d 100644 --- a/src/Yavsc.Server/Models/Blog/BlogPost.cs +++ b/src/Yavsc.Server/Models/Blog/BlogPost.cs @@ -85,7 +85,7 @@ namespace Yavsc.Models.Blog public string[] GetTags() { - return Tags.Select(t => t.Tag.Name).ToArray(); + return Tags?.Select(t => t.Tag.Name).ToArray() ?? Array.Empty(); } [InverseProperty("Post")] @@ -106,6 +106,7 @@ namespace Yavsc.Models.Blog [NotMapped] public bool IsPublished { get; set; } + [JsonIgnore] /// /// Explicit interface implementation of /// . The underlying diff --git a/src/Yavsc.Server/Models/Blog/BlogTag.cs b/src/Yavsc.Server/Models/Blog/BlogTag.cs index 69d428de..1f15a081 100644 --- a/src/Yavsc.Server/Models/Blog/BlogTag.cs +++ b/src/Yavsc.Server/Models/Blog/BlogTag.cs @@ -1,14 +1,17 @@ using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; using Yavsc.Models.Relationship; namespace Yavsc.Models.Blog { public partial class BlogTag { + [JsonIgnore] [ForeignKey("PostId")] public virtual BlogPost Post { get; set; } public long PostId { get; set; } + [JsonIgnore] [ForeignKey("TagId")] public virtual Tag Tag{ get; set; } public long TagId { get; set; } diff --git a/src/Yavsc.Server/Models/Blog/Comment.cs b/src/Yavsc.Server/Models/Blog/Comment.cs index acb5e003..4d39d0c2 100644 --- a/src/Yavsc.Server/Models/Blog/Comment.cs +++ b/src/Yavsc.Server/Models/Blog/Comment.cs @@ -13,15 +13,17 @@ namespace Yavsc.Models.Blog [YaStringLength(1024)] public string Article { get; set; } - - [ForeignKeyAttribute(nameof(ReceiverId))][JsonIgnore] + + [JsonIgnore] + [ForeignKeyAttribute(nameof(ReceiverId))] public virtual BlogPost Post { get; set; } [Required] public long ReceiverId { get; set; } public bool Visible { get; set; } - [ForeignKeyAttribute("AuthorId")][JsonIgnore] + [ForeignKeyAttribute("AuthorId")] + [JsonIgnore] public virtual ApplicationUser Author { get; set; } From 05db88f345441c50883180d8121f38d4c4414eba Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 20:57:52 +0100 Subject: [PATCH 76/95] fixes the 404 on the Details button --- src/Yavsc.Org/Views/Blogspot/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Yavsc.Org/Views/Blogspot/Index.cshtml b/src/Yavsc.Org/Views/Blogspot/Index.cshtml index 52cf3b88..1582041c 100644 --- a/src/Yavsc.Org/Views/Blogspot/Index.cshtml +++ b/src/Yavsc.Org/Views/Blogspot/Index.cshtml @@ -70,7 +70,7 @@
@if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded) { - Details + Details } else { From e56bdd4329a4aa8fe4d9460b136899c8c2f81509 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 20:58:10 +0100 Subject: [PATCH 77/95] dead code cleanup --- src/PostIt/PostIt/Services/UiDispatcher.cs | 72 ---------------------- 1 file changed, 72 deletions(-) delete mode 100644 src/PostIt/PostIt/Services/UiDispatcher.cs diff --git a/src/PostIt/PostIt/Services/UiDispatcher.cs b/src/PostIt/PostIt/Services/UiDispatcher.cs deleted file mode 100644 index e935ac1a..00000000 --- a/src/PostIt/PostIt/Services/UiDispatcher.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Threading.Tasks; -using Avalonia.Threading; - -namespace PostIt.Services; - -/// -/// Tiny marshalling helper around so -/// the rest of the codebase does not have to import Avalonia.Threading -/// directly. We want exactly one place that decides "is the current -/// thread the Avalonia UI thread, and if not, post there" so that -/// -derived types (Settings, the various -/// ViewModels) can fire PropertyChanged safely from background -/// work — which is exactly the cross-thread case that previously blew -/// up inside DataValidationErrors.SetErrors on Avalonia 11. -/// -/// The helper is intentionally tiny: a sync post when we are off the -/// UI thread, a no-op when we are already on it, and an async fire- -/// and-forget variant for places where awaiting would deadlock the -/// caller (e.g. Settings.Load continuation paths). -/// -public static class UiDispatcher -{ - /// - /// True when the calling thread is the Avalonia UI thread. Property - /// setters that touch bindings should check this before mutating - /// state; the safe path is . - /// - public static bool IsOnUiThread => Dispatcher.UIThread.CheckAccess(); - - /// - /// Run on the UI thread. If the caller is - /// already on the UI thread, run synchronously to preserve stack - /// traces and ordering; otherwise post to the dispatcher and wait. - /// Never throws on shutdown — a missing dispatcher is treated as - /// "best-effort skipped", matching Avalonia's own behaviour when - /// the application lifetime has been torn down. - /// - public static void InvokeIfNeeded(Action action) - { - if (action is null) return; - if (IsOnUiThread) { action(); return; } - try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } - catch (InvalidOperationException) { /* dispatcher gone, nothing to do */ } - } - - /// - /// Fire-and-forget variant: schedules on - /// the UI thread but does not block the caller. Use this from - /// background workers (OIDC discovery, HTTP callbacks, file I/O) - /// where awaiting the dispatcher would deadlock the calling sync - /// context. - /// - public static void Post(Action action) - { - if (action is null) return; - try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } - catch (InvalidOperationException) { /* dispatcher gone */ } - } - - /// - /// Awaitable variant. Useful inside async ViewModel methods - /// that must touch bindings only after the dispatcher has processed - /// a queued update (e.g. "load file then refresh observable state"). - /// - public static Task InvokeAsync(Action action) - { - if (action is null) return Task.CompletedTask; - if (IsOnUiThread) { action(); return Task.CompletedTask; } - return Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Normal).GetTask(); - } -} From 1c2e1760b002f1607acda52081639c64f78de471 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 21:33:11 +0100 Subject: [PATCH 78/95] refacto + test fixes --- .vscode/settings.json | 3 +- src/Yavsc.Blogs.Tests/BlogAclApiTests.cs | 3 +- .../BlogApiMappedClaimsTests.cs | 14 +++++-- src/Yavsc.Blogs.Tests/BlogApiTests.cs | 41 ++++++++----------- .../BlogsWebServerFixture.cs | 3 +- src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs | 11 +++++ .../Fixtures/IBackendFixture.cs | 15 +++++++ src/Yavsc.Blogs/Constants.cs | 2 +- 8 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs create mode 100644 src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index 915683fc..1cb334e0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,5 +42,6 @@ "copilotcli/gpt-5.3-codex" ] } - } + }, + "dotnet.defaultSolution": "yavsc.sln" } diff --git a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs index c140c290..4aef805f 100644 --- a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs @@ -131,7 +131,8 @@ public sealed class BlogAclApiTests : IClassFixture BlogPostId = _fixture.PostId }; - var response = await http.PostAsJsonAsync(BlogAclUrl(), payload, + var response = await http.PostAsJsonAsync( + BlogAclUrl(), payload, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, response.StatusCode); diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs index f4878860..7c5747d0 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs @@ -12,10 +12,16 @@ using Yavsc.Tests.Shared; namespace Yavsc.Blogs.Tests; [Collection("JwtClaimMapping")] -public sealed class BlogApiMappedClaimsTests : IClassFixture +public sealed class BlogApiMappedClaimsTests : +IClassFixture, +IBackendFixture { private readonly MappedClaimsBlogsWebServerFixture _fixture; + public IReadOnlyList Addresses => throw new NotImplementedException(); + + public IServiceProvider Services => throw new NotImplementedException(); + public BlogApiMappedClaimsTests(MappedClaimsBlogsWebServerFixture fixture) { _fixture = fixture; @@ -79,7 +85,7 @@ public sealed class BlogApiMappedClaimsTests : IClassFixture(TestContext.Current.CancellationToken); @@ -93,7 +99,7 @@ public sealed class BlogApiMappedClaimsTests : IClassFixture _fixture.SeedUser("tester"); } - /// The fixture's WebApplication is bound to - /// https://localhost:<random> via - /// . We pick the first - /// https URL and append the controller route - /// (/api/v1/blog, matching the production - /// [Route(APIPrefix + "/blog")]). - private string BlogsUrl => - _fixture.Addresses.First(a => a.StartsWith("https://")) + "/api/v1/blog"; - + /// Build an authenticated client: a real /// Authorization: Bearer <jwt> header where the JWT /// is signed by and carries @@ -101,7 +93,8 @@ public sealed class BlogApiTests : IClassFixture _fixture.ResetDatabase(); using var http = NewClient(); - var response = await http.GetAsync("/api/v1/blog", + var response = await http.GetAsync( + _fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -134,7 +127,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -147,7 +140,7 @@ public sealed class BlogApiTests : IClassFixture Assert.Equal(draft.Title, created.Title); // The list should now contain exactly one entry. - var listResponse = await http.GetAsync("/api/v1/blog", + var listResponse = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -175,7 +168,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -185,7 +178,7 @@ public sealed class BlogApiTests : IClassFixture Assert.NotNull(created); Assert.Equal("tester", created!.AuthorId); - var listResponse = await http.GetAsync("/api/v1/blog", + var listResponse = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -213,7 +206,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -262,7 +255,7 @@ public sealed class BlogApiTests : IClassFixture // the framework returns 401. This is the proof that the // production policy is wired in the test host and not // short-circuited by a test-only auth bypass. - var response = await http.GetAsync("/api/v1/blog", + var response = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } @@ -290,7 +283,7 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -309,13 +302,13 @@ public sealed class BlogApiTests : IClassFixture DateCreated = created.DateCreated, DateModified = DateTime.UtcNow }; - var putResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created.Id}", + var putResponse = await http.PutAsJsonAsync(_fixture.BlogUrl()+$"/{created.Id}", update, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); // The list should now reflect the new title. - var listResponse = await http.GetAsync("/api/v1/blog", + var listResponse = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); using var doc = JsonDocument.Parse( @@ -343,19 +336,19 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); var created = (await postResponse.Content.ReadFromJsonAsync( TestContext.Current.CancellationToken ))!; - var deleteResponse = await http.DeleteAsync($"/api/v1/blog/{created.Id}", + var deleteResponse = await http.DeleteAsync(_fixture.BlogUrl()+$"/{created.Id}", TestContext.Current.CancellationToken ); Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); // The list should now be empty. - var listResponse = await http.GetAsync("/api/v1/blog", + var listResponse = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); String response = await listResponse.Content.ReadAsStringAsync( TestContext.Current.CancellationToken @@ -398,7 +391,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync("/api/v1/blog", draft, + var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); // Dump the body on failure so the test name + the response @@ -443,7 +436,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync("/api/v1/blog", draft, + var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); if (response.StatusCode != HttpStatusCode.BadRequest) diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs index 695af4c5..6e8ae31f 100644 --- a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs @@ -10,7 +10,7 @@ using Yavsc.Models.Blog; using Yavsc.Models.Relationship; using Yavsc.Services; using Yavsc.Tests.Shared; - +using static Yavsc.Constants; namespace Yavsc.Blogs.Tests; /// @@ -415,4 +415,5 @@ public sealed class BlogsWebServerFixture : WebHostFixture db.SaveChanges(); return post.Id; } + } diff --git a/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs b/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs new file mode 100644 index 00000000..06e9a5af --- /dev/null +++ b/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs @@ -0,0 +1,11 @@ +namespace Yavsc.Blogs.Tests.Fixtures; + + +public static class BlogHelpers +{ + public static string BlogUrl(this IBackendFixture fixture) + => $"{fixture.Addresses.First(a => a.StartsWith("https://"))}/{Constants.APIPrefix}/{Constants.BlogSpotPath}"; + + public static string BlogAclUrl(this IBackendFixture fixture) + => $"{fixture.Addresses.First(a => a.StartsWith("https://"))}/{Constants.APIPrefix}/{Constants.BlogAclPath}"; +} \ No newline at end of file diff --git a/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs new file mode 100644 index 00000000..61202a0a --- /dev/null +++ b/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs @@ -0,0 +1,15 @@ + +using Yavsc.Blogs.Tests; + +public interface IBackendFixture +{ + /// + /// The addresses the fixture bound to. + /// + IReadOnlyList Addresses { get; } + + /// + /// The service provider for the fixture host. + /// + IServiceProvider Services { get; } +} \ No newline at end of file diff --git a/src/Yavsc.Blogs/Constants.cs b/src/Yavsc.Blogs/Constants.cs index 3e499da4..9d400032 100644 --- a/src/Yavsc.Blogs/Constants.cs +++ b/src/Yavsc.Blogs/Constants.cs @@ -1,6 +1,6 @@ namespace Yavsc.Blogs; -public static class Constants +public static class BlogConstants { public const string AdminRole = "Admin"; public const string ModeratorRole = "Moderator"; From e689dbfe354274d7ad0d219769e44c3be4590bdf Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 22:41:05 +0100 Subject: [PATCH 79/95] fixes the tests --- .../BlogApiMappedClaimsTests.cs | 16 +++++---- src/Yavsc.Blogs.Tests/BlogApiTests.cs | 33 ++++++++++--------- src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs | 11 ------- .../{ => Fixtures}/BlogsWebServerFixture.cs | 0 .../MappedClaimsBlogsWebServerFixture.cs | 2 +- src/Yavsc.Blogs.Tests/PublishEndpointTests.cs | 23 ++++++------- src/Yavsc.Server/Helpers/PayloadHelpers.cs | 5 +-- src/Yavsc.Tests.Shared/BlogHelpers.cs | 31 +++++++++++++++++ .../IBackendFixture.cs | 6 ++-- src/Yavsc.Tests.Shared/WebHostFixture.cs | 2 +- .../Yavsc.Tests.Shared.csproj | 5 ++- 11 files changed, 79 insertions(+), 55 deletions(-) delete mode 100644 src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs rename src/Yavsc.Blogs.Tests/{ => Fixtures}/BlogsWebServerFixture.cs (100%) rename src/Yavsc.Blogs.Tests/{ => Fixtures}/MappedClaimsBlogsWebServerFixture.cs (99%) create mode 100644 src/Yavsc.Tests.Shared/BlogHelpers.cs rename src/{Yavsc.Blogs.Tests/Fixtures => Yavsc.Tests.Shared}/IBackendFixture.cs (80%) diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs index 7c5747d0..f6b46ba9 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs @@ -8,11 +8,12 @@ using Microsoft.IdentityModel.Tokens; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; +using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; [Collection("JwtClaimMapping")] -public sealed class BlogApiMappedClaimsTests : +public sealed class BlogApiMappedClaimsTests : IClassFixture, IBackendFixture { @@ -85,7 +86,10 @@ IBackendFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); + var response = await http.PostAsJsonAsync( + _fixture.BlogSpotUrl(), + draft, + TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, response.StatusCode); var created = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); @@ -99,7 +103,7 @@ IBackendFixture ResetDatabase(); using var http = NewClient(subject: "mapped-owner"); - var createdResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), new BlogPost + var createdResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), new BlogPost { Id = 0, Title = "Billet à modifier", @@ -113,7 +117,7 @@ IBackendFixture var created = await createdResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); Assert.NotNull(created); - var updateResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost + var updateResponse = await http.PutAsJsonAsync(_fixture.BlogSpotUrl() + $"/{created!.Id}", new BlogPost { Id = created.Id, Title = "Billet modifié", @@ -132,7 +136,7 @@ IBackendFixture ResetDatabase(); using var ownerHttp = NewClient(subject: "mapped-owner"); - var createdResponse = await ownerHttp.PostAsJsonAsync(_fixture.BlogUrl(), new BlogPost + var createdResponse = await ownerHttp.PostAsJsonAsync(_fixture.BlogSpotUrl(), new BlogPost { Id = 0, Title = "Billet protégé", @@ -147,7 +151,7 @@ IBackendFixture Assert.NotNull(created); using var otherHttp = NewClient(subject: "mapped-other"); - var updateResponse = await otherHttp.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost + var updateResponse = await otherHttp.PutAsJsonAsync(_fixture.BlogSpotUrl() + $"/{created!.Id}", new BlogPost { Id = created.Id, Title = "Tentative de modification", diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs index f188912f..11f5c0a2 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs @@ -6,6 +6,7 @@ using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Server.Helpers; using Yavsc.Tests.Shared; +using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; @@ -46,7 +47,7 @@ public sealed class BlogApiTests : IClassFixture _fixture.SeedUser("tester"); } - + /// Build an authenticated client: a real /// Authorization: Bearer <jwt> header where the JWT /// is signed by and carries @@ -94,7 +95,7 @@ public sealed class BlogApiTests : IClassFixture using var http = NewClient(); var response = await http.GetAsync( - _fixture.BlogUrl(), + _fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -127,7 +128,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -140,7 +141,7 @@ public sealed class BlogApiTests : IClassFixture Assert.Equal(draft.Title, created.Title); // The list should now contain exactly one entry. - var listResponse = await http.GetAsync(_fixture.BlogUrl(), + var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -168,7 +169,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -178,7 +179,7 @@ public sealed class BlogApiTests : IClassFixture Assert.NotNull(created); Assert.Equal("tester", created!.AuthorId); - var listResponse = await http.GetAsync(_fixture.BlogUrl(), + var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -206,7 +207,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -255,7 +256,7 @@ public sealed class BlogApiTests : IClassFixture // the framework returns 401. This is the proof that the // production policy is wired in the test host and not // short-circuited by a test-only auth bypass. - var response = await http.GetAsync(_fixture.BlogUrl(), + var response = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } @@ -283,7 +284,7 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -302,13 +303,13 @@ public sealed class BlogApiTests : IClassFixture DateCreated = created.DateCreated, DateModified = DateTime.UtcNow }; - var putResponse = await http.PutAsJsonAsync(_fixture.BlogUrl()+$"/{created.Id}", + var putResponse = await http.PutAsJsonAsync(_fixture.BlogSpotUrl()+$"/{created.Id}", update, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); // The list should now reflect the new title. - var listResponse = await http.GetAsync(_fixture.BlogUrl(), + var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); using var doc = JsonDocument.Parse( @@ -336,19 +337,19 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); var created = (await postResponse.Content.ReadFromJsonAsync( TestContext.Current.CancellationToken ))!; - var deleteResponse = await http.DeleteAsync(_fixture.BlogUrl()+$"/{created.Id}", + var deleteResponse = await http.DeleteAsync(_fixture.BlogSpotUrl()+$"/{created.Id}", TestContext.Current.CancellationToken ); Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); // The list should now be empty. - var listResponse = await http.GetAsync(_fixture.BlogUrl(), + var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); String response = await listResponse.Content.ReadAsStringAsync( TestContext.Current.CancellationToken @@ -391,7 +392,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var response = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); // Dump the body on failure so the test name + the response @@ -436,7 +437,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var response = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); if (response.StatusCode != HttpStatusCode.BadRequest) diff --git a/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs b/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs deleted file mode 100644 index 06e9a5af..00000000 --- a/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Yavsc.Blogs.Tests.Fixtures; - - -public static class BlogHelpers -{ - public static string BlogUrl(this IBackendFixture fixture) - => $"{fixture.Addresses.First(a => a.StartsWith("https://"))}/{Constants.APIPrefix}/{Constants.BlogSpotPath}"; - - public static string BlogAclUrl(this IBackendFixture fixture) - => $"{fixture.Addresses.First(a => a.StartsWith("https://"))}/{Constants.APIPrefix}/{Constants.BlogAclPath}"; -} \ No newline at end of file diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs similarity index 100% rename from src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs diff --git a/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs similarity index 99% rename from src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs index 127f38fe..2546de0a 100644 --- a/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs @@ -21,7 +21,7 @@ namespace Yavsc.Blogs.Tests; /// This is the closest in-process reproduction of the production /// authentication surface for the blog API. /// -public sealed class MappedClaimsBlogsWebServerFixture : IDisposable +public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFixture { private readonly InMemoryDatabaseRoot _inMemoryRoot = new(); private readonly Dictionary _savedInboundMap; diff --git a/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs index 7d5a02a9..113707ca 100644 --- a/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs +++ b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; +using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; @@ -71,12 +72,6 @@ public sealed class PublishEndpointTests : IClassFixture return post.Id; } - private string PublishUrl(long id) - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/api/v1/blog/{id}/publish"; - - private string BlogsUrl - => _fixture.Addresses.First(a => a.StartsWith("https://")) + "/api/v1/blog"; - private HttpClient NewClient(string subject) { var handler = new HttpClientHandler @@ -100,12 +95,13 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("alice"); - var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); - var get = await http.GetAsync($"{BlogsUrl}/{postId}", TestContext.Current.CancellationToken); + var get = await http.GetAsync(_fixture.BlogSpotUrl() + $"/{postId}", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, get.StatusCode); using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + Assert.Equal($"post-by-alice", doc.RootElement.GetProperty("title").GetString()); Assert.True(doc.RootElement.GetProperty("isPublished").GetBoolean()); } @@ -116,11 +112,12 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("alice"); - await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); - var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = false }, TestContext.Current.CancellationToken); + await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = false }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); - var get = await http.GetAsync($"{BlogsUrl}/{postId}", TestContext.Current.CancellationToken); + var get = await http.GetAsync(_fixture.BlogSpotUrl() + $"/{postId}", TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.OK, get.StatusCode); using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); Assert.False(doc.RootElement.GetProperty("isPublished").GetBoolean()); } @@ -130,7 +127,7 @@ public sealed class PublishEndpointTests : IClassFixture { ResetDatabase(); using var http = NewClient("alice"); - var put = await http.PutAsJsonAsync(PublishUrl(99999L), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(_fixture.PublishUrl(99999L), new { publish = true }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NotFound, put.StatusCode); } @@ -141,7 +138,7 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("bob"); - var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); // 401 Challenge (the controller returns Challenge() // for AuthorizationFailureException). The exact code // is framework-dependent; what matters is "not 204". diff --git a/src/Yavsc.Server/Helpers/PayloadHelpers.cs b/src/Yavsc.Server/Helpers/PayloadHelpers.cs index 293cab02..cd359bb4 100644 --- a/src/Yavsc.Server/Helpers/PayloadHelpers.cs +++ b/src/Yavsc.Server/Helpers/PayloadHelpers.cs @@ -15,7 +15,8 @@ public static class PayloadHelpers post.UserModified, post.AuthorId, ACL = post.GetACL(), - Tags = post.GetTags() + Tags = post.GetTags(), + post.IsPublished }; } -} \ No newline at end of file +} diff --git a/src/Yavsc.Tests.Shared/BlogHelpers.cs b/src/Yavsc.Tests.Shared/BlogHelpers.cs new file mode 100644 index 00000000..ef5a7688 --- /dev/null +++ b/src/Yavsc.Tests.Shared/BlogHelpers.cs @@ -0,0 +1,31 @@ + +namespace Yavsc.Blogs.Tests.Fixtures; +using static Yavsc.Constants; + +public static class BlogHelpers +{ + public static string ApiUrl(this IBackendFixture fixture, string apiSubPath) + { + var secured = fixture.Addresses.FirstOrDefault(a => a.StartsWith("https://")); + if (secured is null) + { + var unsecured = fixture.Addresses.FirstOrDefault(a => a.StartsWith("http://")); + if (unsecured is null) + { + throw new InvalidOperationException("No backend address found"); + } + return $"{unsecured}/{APIPrefix}/{apiSubPath}"; + } + return $"{secured}/{APIPrefix}/{apiSubPath}"; + } + + public static string BlogAclUrl(this IBackendFixture fixture) + => fixture.ApiUrl(BlogAclPath); + + public static string BlogSpotUrl(this IBackendFixture fixture) + => fixture.ApiUrl(BlogSpotPath); + + public static string PublishUrl(this IBackendFixture fixture, long id) + => fixture.ApiUrl(BlogSpotPath) +"/" + id + "/publish"; + +} diff --git a/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs b/src/Yavsc.Tests.Shared/IBackendFixture.cs similarity index 80% rename from src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs rename to src/Yavsc.Tests.Shared/IBackendFixture.cs index 61202a0a..4e2f009d 100644 --- a/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs +++ b/src/Yavsc.Tests.Shared/IBackendFixture.cs @@ -1,7 +1,5 @@ -using Yavsc.Blogs.Tests; - -public interface IBackendFixture +public interface IBackendFixture { /// /// The addresses the fixture bound to. @@ -12,4 +10,4 @@ public interface IBackendFixture /// The service provider for the fixture host. /// IServiceProvider Services { get; } -} \ No newline at end of file +} diff --git a/src/Yavsc.Tests.Shared/WebHostFixture.cs b/src/Yavsc.Tests.Shared/WebHostFixture.cs index 417d29d8..95b23082 100644 --- a/src/Yavsc.Tests.Shared/WebHostFixture.cs +++ b/src/Yavsc.Tests.Shared/WebHostFixture.cs @@ -30,7 +30,7 @@ namespace Yavsc.Tests.Shared; /// mapping are the responsibility of the subclass, through /// . /// -public abstract class WebHostFixture : IDisposable +public abstract class WebHostFixture : IDisposable, IBackendFixture { private static readonly Lazy _selfSignedCertificate = new Lazy(CreateSelfSignedCertificate); diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index 7bd8f24a..6e7a4c5a 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -23,4 +23,7 @@ - \ No newline at end of file + + + + From dc05266bdb742762385120f88bc60cff5c303b08 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 23:17:58 +0100 Subject: [PATCH 80/95] Release the android x86 package --- .forgejo/workflows/release.yml | 52 +++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 930c97b9..22b14232 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -173,37 +173,34 @@ jobs: echo "EOF" >> "$GITHUB_ENV" echo "IS_PRERELEASE=$([ "$CHANNEL" = "stable" ] && echo false || echo true)" >> "$GITHUB_ENV" - - name: Build des projets .NET (sans docker) - # L'image runner (pazof/yavsc-build-env) a le SDK .NET 10 + le - # workload Android, mais PAS le binaire `docker` ni de daemon - # Docker. On exécute donc les commandes dotnet directement - # au lieu de passer par `docker build`. - # Equivalent des stages build-env du Dockerfile (lignes - # restore + build Yavsc.Org + build Yavsc.Api + build - # Yavsc.Blogs + build PostIt.Android -r android-arm64). + - name: Build des projets .NET + run: | cd /src/_src - dotnet restore - dotnet build src/Yavsc.Org/Yavsc.Org.csproj -c Release --no-restore -clp:ErrorsOnly - dotnet build src/Yavsc.Api/Yavsc.Api.csproj -c Release --no-restore -clp:ErrorsOnly - dotnet build src/Yavsc.Blogs/Yavsc.Blogs.csproj -c Release --no-restore -clp:ErrorsOnly - dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release --no-restore -clp:ErrorsOnly -r android-arm64 + dotnet build yavsc.sln -c Release --no-restore -clp:ErrorsOnly - - name: Copier l'APK signé vers un emplacement connu + - name: Copier les APK signés vers un emplacement connu # Le build Android avec -r android-arm64 produit l'APK dans # bin/Release/net10.0-android/android-arm64/. On le copie à # la racine du checkout pour que l'étape d'upload le trouve. run: | cd /src/_src - APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk - if [[ ! -f "$APK" ]]; then - echo "::error::APK not found at $APK" + ARMAPK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk + if [[ ! -f "$ARMAPK" ]]; then + echo "::error::ARMAPK not found at $ARMAPK" ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64 2>/dev/null || true exit 1 fi - cp "$APK" /src/_src/PostIt.Android.apk - ls -la /src/_src/PostIt.Android.apk + cp "$ARMAPK" /src/_src/PostIt.Android-arm64.apk + XAPK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-x86_64/fr.pschneider.postit-Signed.apk + if [[ ! -f "$XAPK" ]]; then + echo "::error::XAPK not found at $XAPK" + ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-x86_64 2>/dev/null || true + exit 1 + fi + cp "$XAPK" /src/_src/PostIt.Android-x86_64.apk + ls -la /src/_src/PostIt.Android-arm64.apk + ls -la /src/_src/PostIt.Android-x86_64.apk - name: Publier la release Forgejo via l'API REST # Pas d'action tierce (pas de Node dans l'image runner). @@ -312,15 +309,24 @@ jobs: # sinon curl l'interprète comme un second fichier d'input # (un fichier nommé '?name=PostIt.Android.apk') et l'API # Forgejo renvoie 400 "Missing 'name' parameter". - echo "::group::Upload APK asset" + echo "::group::Upload PostIt APK assets" HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \ -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Content-Type: application/octet-stream" \ -H "Accept: application/json" \ - --data-binary "@/src/_src/PostIt.Android.apk" \ - "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android.apk") + --data-binary "@/src/_src/PostIt.Android-arm64.apk" \ + "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android-arm64.apk") echo "POST asset -> HTTP $HTTP" + HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + -H "Accept: application/json" \ + --data-binary "@/src/_src/PostIt.Android-x86_64.apk" \ + "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android-x86_64.apk") + echo "POST asset -> HTTP $HTTP" + echo "::endgroup::" if [[ "$HTTP" != "201" ]]; then From 1cdb9619a0ea9ee1f114458ecd50649cc1978e60 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 23:35:06 +0100 Subject: [PATCH 81/95] reference my upstream --- CHANGELOG.md | 10 +++++----- CONTRIBUTING.md | 2 +- README.md | 6 +++--- src/Yavsc.Abstract/Yavsc.Abstract.csproj | 2 +- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 2 +- src/Yavsc.Org/Views/Home/About.pt.cshtml | 12 ++++++------ src/Yavsc.Server/Yavsc.Server.csproj | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8425c2c6..31098958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -197,10 +197,10 @@ Today, assigning ACL succeeds the first time, the following times result in a 40 migration, reverted in this release. The publish toggle covers the same user-visible switch without a schema change. -[Unreleased]: https://github.com/pazof/yavsc/compare/HEAD -[1.0.8-rc1]: https://github.com/pazof/yavsc/compare/1.0.7...1.0.8-rc1 -[1.0.7]: https://github.com/pazof/yavsc/compare/1.0.6...1.0.7 -[1.0.6]: https://github.com/pazof/yavsc/compare/1.0.5...1.0.6 +[Unreleased]: https://forgejo.pschneider.fr/notazof/yavsc/compare/HEAD +[1.0.8-rc1]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.7...1.0.8-rc1 +[1.0.7]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.6...1.0.7 +[1.0.6]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.5...1.0.6 ## [1.0.6] - stable @@ -234,4 +234,4 @@ Today, assigning ACL succeeds the first time, the following times result in a 40 actual release id. Switched to `jq` for both body construction and field extraction. -[1.0.6]: https://github.com/pazof/yavsc/compare/1.0.5...1.0.6 +[1.0.6]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.5...1.0.6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e528b7a4..b9969ea5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ ## Premier build ```bash -git clone https://github.com/pazof/yavsc.git +git clone https://forgejo.pschneider.fr/notazof/yavsc.git cd yavsc dotnet restore dotnet build diff --git a/README.md b/README.md index 8e630612..9adcb57a 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml # Statut actuel des actions GitHub -* [![Build and Push Yavsc Apk](https://github.com/pazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/docker-publish-android.yml) +* [![Build and Push Yavsc Apk](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml) -* [![Build and Push Yavsc Production Image](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml) +* [![Build and Push Yavsc Production Image](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml) -* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) +* [![CodeQL Advanced](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/codeql.yml) # Documentation diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 5094b14d..19f0e163 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -5,7 +5,7 @@ A shared model for a little client/server app, dealing about establishing some contract, between some human client and provider. Yavsc.Abstract - https://github.com/pazof/yavsc + https://forgejo.pschneider.fr/notazof/yavsc true true latest diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 8692b28a..3c096762 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -4,7 +4,7 @@ enable 1c73094f-959f-4211-b1a1-6a69b236c283 Yavsc.Blogs - https://github.com/pazof/yavsc + https://forgejo.pschneider.fr/notazof/yavsc true 1.1.0.0 1.1.0.0 diff --git a/src/Yavsc.Org/Views/Home/About.pt.cshtml b/src/Yavsc.Org/Views/Home/About.pt.cshtml index cca60a94..487a1ab3 100755 --- a/src/Yavsc.Org/Views/Home/About.pt.cshtml +++ b/src/Yavsc.Org/Views/Home/About.pt.cshtml @@ -93,8 +93,8 @@ A operação é anulável até duas semanas após a sua programação. Este é o meu site perso, uma configuração de _Yavsc_ (outro negócio muito pequeno). -* [README](https://github.com/pazof/yavsc/blob/vnext/README.md) -* [licença: GNU GPL v3](https://github.com/pazof/yavsc/blob/vnext/LICENSE) +* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) +* [licença: GNU GPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) Outras instalações: @@ -109,8 +109,8 @@ Outras instalações: Yet Another Very Small Company ... -* [README](https://github.com/pazof/yavsc/blob/vnext/README.md) -* [license: GNU FPL v3](https://github.com/pazof/yavsc/blob/vnext/LICENSE) +* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) +* [license: GNU FPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) @@ -118,8 +118,8 @@ Outras instalações: ## Yet Another Very Small Company : -* [README](https://github.com/pazof/yavsc/blob/vnext/README.md) -* [license: GNU FPL v3](https://github.com/pazof/yavsc/blob/vnext/LICENSE) +* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) +* [license: GNU FPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) En production: diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index 839e476a..53382ec1 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -5,7 +5,7 @@ 53bd70e8-ff81-497a-847f-a15fd8ea7a09 Yavsc.Server true - https://github.com/pazof/yavsc + https://forgejo.pschneider.fr/notazof/yavsc true 1.1.0.0 1.1.0.0 From 06b33f18e47748e39e2a7cafffb4638fa8d9902d Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 23:43:44 +0100 Subject: [PATCH 82/95] keep the CodeQL Advanced badge --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 9adcb57a..d9e9dfa7 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,8 @@ https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml # Statut actuel des actions GitHub -* [![Build and Push Yavsc Apk](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml) +-* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) -* [![Build and Push Yavsc Production Image](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml) - -* [![CodeQL Advanced](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/codeql.yml) # Documentation From d73800267702c38dc6e77dd9ea093eb7f4750581 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 00:04:59 +0100 Subject: [PATCH 83/95] build the x64 and arm64 together, in the same apk --- .forgejo/workflows/release.yml | 41 +++++++++++++--------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 22b14232..0b909b01 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -177,7 +177,15 @@ jobs: run: | cd /src/_src - dotnet build yavsc.sln -c Release --no-restore -clp:ErrorsOnly + dotnet restore + dotnet build src/Yavsc.Org/Yavsc.Org.csproj \ + -c Release --no-restore -clp:ErrorsOnly + dotnet build src/Yavsc.Api/Yavsc.Api.csproj \ + -c Release --no-restore -clp:ErrorsOnly + dotnet build src/Yavsc.Blogs/Yavsc.Blogs.csproj \ + -c Release --no-restore -clp:ErrorsOnly + dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ + -c Release --no-restore -clp:ErrorsOnly - name: Copier les APK signés vers un emplacement connu # Le build Android avec -r android-arm64 produit l'APK dans @@ -185,22 +193,13 @@ jobs: # la racine du checkout pour que l'étape d'upload le trouve. run: | cd /src/_src - ARMAPK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk - if [[ ! -f "$ARMAPK" ]]; then - echo "::error::ARMAPK not found at $ARMAPK" - ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64 2>/dev/null || true + APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/fr.pschneider.postit-Signed.apk + if [[ ! -f "$APK" ]]; then + echo "::error::APK not found at $APK" + ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android 2>/dev/null || true exit 1 fi - cp "$ARMAPK" /src/_src/PostIt.Android-arm64.apk - XAPK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-x86_64/fr.pschneider.postit-Signed.apk - if [[ ! -f "$XAPK" ]]; then - echo "::error::XAPK not found at $XAPK" - ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-x86_64 2>/dev/null || true - exit 1 - fi - cp "$XAPK" /src/_src/PostIt.Android-x86_64.apk - ls -la /src/_src/PostIt.Android-arm64.apk - ls -la /src/_src/PostIt.Android-x86_64.apk + cp "$APK" /src/_src/PostIt.Android.apk - name: Publier la release Forgejo via l'API REST # Pas d'action tierce (pas de Node dans l'image runner). @@ -315,16 +314,8 @@ jobs: -H "Authorization: token $GITHUB_TOKEN" \ -H "Content-Type: application/octet-stream" \ -H "Accept: application/json" \ - --data-binary "@/src/_src/PostIt.Android-arm64.apk" \ - "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android-arm64.apk") - echo "POST asset -> HTTP $HTTP" - HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \ - -X POST \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H "Content-Type: application/octet-stream" \ - -H "Accept: application/json" \ - --data-binary "@/src/_src/PostIt.Android-x86_64.apk" \ - "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android-x86_64.apk") + --data-binary "@/src/_src/PostIt.Android.apk" \ + "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android.apk") echo "POST asset -> HTTP $HTTP" echo "::endgroup::" From 589e2ba6c33d2371423b2b4f17e1156614082266 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 00:27:39 +0100 Subject: [PATCH 84/95] release it --- .forgejo/workflows/release.yml | 52 +++--- .github/workflows/docker-publish-android.yml | 183 ------------------- 2 files changed, 28 insertions(+), 207 deletions(-) delete mode 100644 .github/workflows/docker-publish-android.yml diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 0b909b01..d7431790 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -185,7 +185,9 @@ jobs: dotnet build src/Yavsc.Blogs/Yavsc.Blogs.csproj \ -c Release --no-restore -clp:ErrorsOnly dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release --no-restore -clp:ErrorsOnly + -c Release --no-restore -clp:ErrorsOnly -r android-arm64 + dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ + -c Release --no-restore -clp:ErrorsOnly -r android-x86_64 - name: Copier les APK signés vers un emplacement connu # Le build Android avec -r android-arm64 produit l'APK dans @@ -193,13 +195,15 @@ jobs: # la racine du checkout pour que l'étape d'upload le trouve. run: | cd /src/_src - APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/fr.pschneider.postit-Signed.apk - if [[ ! -f "$APK" ]]; then - echo "::error::APK not found at $APK" - ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android 2>/dev/null || true - exit 1 - fi - cp "$APK" /src/_src/PostIt.Android.apk + for MARCH in arm64 x64; do + APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-$MARCH/fr.pschneider.postit-Signed.apk + if [[ ! -f "$APK" ]]; then + echo "::error::APK not found at $APK" + ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android 2>/dev/null || true + exit 1 + fi + cp "$APK" /src/_src/PostIt.Android-$MARCH.apk + done - name: Publier la release Forgejo via l'API REST # Pas d'action tierce (pas de Node dans l'image runner). @@ -309,21 +313,21 @@ jobs: # (un fichier nommé '?name=PostIt.Android.apk') et l'API # Forgejo renvoie 400 "Missing 'name' parameter". echo "::group::Upload PostIt APK assets" - HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \ - -X POST \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H "Content-Type: application/octet-stream" \ - -H "Accept: application/json" \ - --data-binary "@/src/_src/PostIt.Android.apk" \ - "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android.apk") - echo "POST asset -> HTTP $HTTP" - + for MARCH in arm64 x64; do + HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + -H "Accept: application/json" \ + --data-binary "@/src/_src/PostIt.Android-$MARCH.apk" \ + "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android-$MARCH.apk") + echo "POST asset -> HTTP $HTTP" + if [[ "$HTTP" != "201" ]]; then + echo "::error::Asset upload failed (HTTP $HTTP):" + cat /tmp/asset.json + exit 1 + fi + done echo "::endgroup::" - if [[ "$HTTP" != "201" ]]; then - echo "::error::Asset upload failed (HTTP $HTTP):" - cat /tmp/asset.json - exit 1 - fi - - echo "Release publiée: $API_BASE/$GITHUB_REPOSITORY/releases/tag/$TAG" + echo "✅ Release publiée: $API_BASE/$GITHUB_REPOSITORY/releases/tag/$TAG" diff --git a/.github/workflows/docker-publish-android.yml b/.github/workflows/docker-publish-android.yml deleted file mode 100644 index a4ce15aa..00000000 --- a/.github/workflows/docker-publish-android.yml +++ /dev/null @@ -1,183 +0,0 @@ -name: Build and Push Yavsc Apk - -on: - push: - branches: - - main - tags: - - '*' - workflow_dispatch: - inputs: - force_unstable: - description: 'Publier une release avec suffixe (ex. 1.0.0-rc1) malgré le fail-fast par défaut.' - required: false - type: boolean - default: false - -# softprops/action-gh-release a besoin de contents: write -# pour publier une release + uploader un asset. -permissions: - contents: write - -jobs: - apk-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout du code - uses: actions/checkout@v7 - with: - fetch-depth: 0 - fetch-tags: true - - # 1. Votre étape de build actuelle (on nomme l'image "postit-android") - # --target build-env : on ne veut que le stage de build (qui - # contient les artefacts .apk). Sans --target, Docker ciblerait - # le DERNIER stage du Dockerfile (blogs-runtime, qui est une - # image ASP.NET runtime sans aucun APK à extraire). - - name: Build de l'image Docker - run: docker build --build-arg ANDROID_TARGET_RID=android-arm64 --target build-env -t postit-android . - # 2. EXTRACTION : Créer un conteneur éphémère pour copier l'APK vers l'hôte GitHub - - name: Extraire l'APK du conteneur Docker - run: | - docker create --name extractor postit-android - docker cp extractor:/src/src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/fr.pschneider.postit-Signed.apk ./PostIt.Android.apk - docker rm extractor - - - name: Téléverser l'APK en tant qu'Artéfact GitHub - uses: actions/upload-artifact@v7 - with: - name: application-apk-release - path: ./PostIt.Android.apk - retention-days: 7 - - # Job de validation : parse le tag, vérifie le format, applique la règle - # de parité du patch (pair=stable / impair=preview / suffixe=instable), - # et s'assure que CHANGELOG.md contient une section cohérente. - # Sans ce job, le job publish-release peut être bypassé (un attaquant - # qui contrôle un tag ne peut pas publier de release sans une section - # changelog cohérente). - validate-release: - if: startsWith(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - steps: - - name: Checkout du code - uses: actions/checkout@v7 - with: - fetch-depth: 0 - fetch-tags: true - - - name: Valider le tag et la section CHANGELOG - env: - FORCE_UNSTABLE: ${{ inputs.force_unstable || github.event.inputs.force_unstable || 'false' }} - run: | - TAG="${GITHUB_REF_NAME}" - - # Parse semver : MAJOR.MINOR.PATCH[-SUFFIX] - if [[ ! "$TAG" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then - echo "::error::Tag '$TAG' does not match MAJOR.MINOR.PATCH[-SUFFIX] format." - exit 1 - fi - - MAJOR="${BASH_REMATCH[1]}" - MINOR="${BASH_REMATCH[2]}" - PATCH="${BASH_REMATCH[3]}" - SUFFIX="${BASH_REMATCH[4]}" - - # Classification du canal par parité du patch. - # Patch pair + pas de suffixe -> stable. - # Patch impair + pas de suffixe -> preview. - # Suffixe présent -> instable. - if [[ -n "$SUFFIX" ]]; then - CHANNEL="unstable" - elif (( PATCH % 2 == 0 )); then - CHANNEL="stable" - else - CHANNEL="preview" - fi - - echo "Tag $TAG classifié comme channel=$CHANNEL" - - # Fail-fast sur instable sauf opt-in explicite via workflow_dispatch. - if [[ "$CHANNEL" == "unstable" && "$FORCE_UNSTABLE" != "true" ]]; then - echo "::error::Tag '$TAG' is unstable (suffix '$SUFFIX'). Refusing to publish." - echo "Set force_unstable=true via workflow_dispatch to override." - exit 1 - fi - - # Lecture du CHANGELOG.md (doit exister à la racine du repo). - if [[ ! -f CHANGELOG.md ]]; then - echo "::error::CHANGELOG.md not found at repo root." - exit 1 - fi - - # Extraction de la section [TAG]. On cherche la première ligne - # commençant par '## [' qui contient '[TAG]' (entre '## [' et - # la prochaine ligne '## [' ou fin de fichier). awk en mode - # paragraphe suffit et reste POSIX. - BODY=$(awk -v tag="[$TAG]" ' - /^## \[/ { - if (in_section) exit - if (index($0, tag) > 0) in_section=1 - next - } - in_section { print } - ' CHANGELOG.md) - - if [[ -z "$BODY" ]]; then - echo "::error::No section matching '## [$TAG]' found in CHANGELOG.md." - echo "Add a '## [$TAG] - $CHANNEL' section before tagging." - exit 1 - fi - - # Vérification cohérence du canal déclaré dans le titre de section. - # Format attendu : "## [TAG] - stable" / "- preview" / "- unstable". - HEADER=$(grep -m1 "^## \[$TAG\]" CHANGELOG.md) - if [[ "$HEADER" != *" - $CHANNEL"* ]]; then - echo "::error::Section '## [$TAG]' must declare suffix '- $CHANNEL' to match tag parity." - echo "Current section header: $HEADER" - exit 1 - fi - - echo "Section CHANGELOG validée pour [$TAG] - $CHANNEL" - - # Exposition aux étapes suivantes via $GITHUB_ENV. - # heredoc <> "$GITHUB_ENV" - - publish-release: - # Déclenché uniquement par un push de tag. Le job apk-deploy produit - # l'artefact ; validate-release garantit la cohérence du tag et du - # changelog avant publication. - if: startsWith(github.ref, 'refs/tags/') - needs: [apk-deploy, validate-release] - runs-on: ubuntu-latest - steps: - - name: Récupérer l'APK depuis l'artefact - uses: actions/download-artifact@v7 - with: - name: application-apk-release - path: ./ - - - name: Publier la release GitHub et uploader l'APK - uses: softprops/action-gh-release@v2 - with: - # Le nom de fichier final dans la release. C'est ce qui - # apparaîtra dans l'asset et donc dans le permalink : - # https://github.com///releases/latest/download/PostIt.Android.apk - files: ./PostIt.Android.apk - # Le body est extrait de la section CHANGELOG.md correspondant - # au tag, exposée par validate-release via $GITHUB_ENV. - body: ${{ env.RELEASE_BODY }} - # stable -> false (marque comme Latest). - # preview / unstable -> true (visible mais pas Latest). - prerelease: ${{ env.IS_PRERELEASE }} From 74cb8795d2b3d23f9cb4551bb49a292273cad954 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 00:39:20 +0100 Subject: [PATCH 85/95] x64 ! --- .forgejo/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index d7431790..1882b7d9 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -187,7 +187,7 @@ jobs: dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ -c Release --no-restore -clp:ErrorsOnly -r android-arm64 dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release --no-restore -clp:ErrorsOnly -r android-x86_64 + -c Release --no-restore -clp:ErrorsOnly -r android-x64 - name: Copier les APK signés vers un emplacement connu # Le build Android avec -r android-arm64 produit l'APK dans From 8226f54074d3bcebc0bb86dbafd02da0bf1eb3f6 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 00:52:29 +0100 Subject: [PATCH 86/95] -MsBuild.GitVersion --- .forgejo/workflows/release.yml | 4 +--- Directory.Build.props | 10 ---------- Directory.Packages.props | 1 - src/Yavsc.Abstract/Yavsc.Abstract.csproj | 3 --- src/Yavsc.Api.Client/Yavsc.Api.Client.csproj | 3 --- src/Yavsc.Api/Yavsc.Api.csproj | 3 --- src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj | 3 --- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 3 --- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 3 --- src/Yavsc.Org/Yavsc.Org.csproj | 3 --- src/Yavsc.Server/Yavsc.Server.csproj | 3 --- src/cli/cli.csproj | 3 --- 12 files changed, 1 insertion(+), 41 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 1882b7d9..4d79acee 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -68,10 +68,8 @@ jobs: # WORKDIR de l'image (cf. dotnet-android-build-image/Dockerfile). cd /src - # Clone unshallow pour que GitVersion.MsBuild ait l'historique - # et les tags (sinon MSB3073 sur la cible Android cf. PR #21). if [[ ! -d _src/.git ]]; then - git clone https://forgejo.pschneider.fr/notazof/yavsc.git _src + git clone --depth=1 https://forgejo.pschneider.fr/notazof/yavsc.git _src fi cd _src diff --git a/Directory.Build.props b/Directory.Build.props index 0ce825bd..83d21579 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,16 +1,6 @@ Yavsc - - true NU1701, NU1901, NU1902, NU1507 diff --git a/Directory.Packages.props b/Directory.Packages.props index 84380e44..7505c851 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,6 @@ - diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 19f0e163..4a9c75bc 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -14,7 +14,4 @@ 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 - - - \ No newline at end of file diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index 4c560f23..44e4eb27 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -20,9 +20,6 @@ 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 - - - diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index 3655079b..20bc2eb6 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -14,7 +14,4 @@ - - - \ No newline at end of file diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index bc4e34ba..7763cf07 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -32,7 +32,4 @@ - - - \ No newline at end of file diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 3c096762..834bc57f 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -15,7 +15,4 @@ - - - \ No newline at end of file diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 8019a78f..747498d4 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -86,7 +86,4 @@ - - - \ No newline at end of file diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index 6dbceb4a..a382dd09 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -51,7 +51,4 @@ - - - \ No newline at end of file diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index 53382ec1..2d7ba0fa 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -40,7 +40,4 @@ - - - \ No newline at end of file diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index 49aa64b1..209440d8 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -22,7 +22,4 @@ - - - \ No newline at end of file From 4988253fdf790b33e88ea6af50f7af9154682a9f Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 01:06:24 +0100 Subject: [PATCH 87/95] detail the release action --- .forgejo/workflows/release.yml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 4d79acee..ba0f2c89 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -171,21 +171,22 @@ jobs: echo "EOF" >> "$GITHUB_ENV" echo "IS_PRERELEASE=$([ "$CHANNEL" = "stable" ] && echo false || echo true)" >> "$GITHUB_ENV" - - name: Build des projets .NET - + - name: Restore run: | cd /src/_src dotnet restore - dotnet build src/Yavsc.Org/Yavsc.Org.csproj \ - -c Release --no-restore -clp:ErrorsOnly - dotnet build src/Yavsc.Api/Yavsc.Api.csproj \ - -c Release --no-restore -clp:ErrorsOnly - dotnet build src/Yavsc.Blogs/Yavsc.Blogs.csproj \ - -c Release --no-restore -clp:ErrorsOnly - dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release --no-restore -clp:ErrorsOnly -r android-arm64 - dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release --no-restore -clp:ErrorsOnly -r android-x64 + + - name: Build de PostIt.Android ARM64 + run: | + cd /src/_src + dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ + -c Release -r android-arm64 --no-restore -clp:ErrorsOnly + + - name: Build de PostIt.Android x64 + run: | + cd /src/_src + dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ + -c Release -r android-x64 --no-restore -clp:ErrorsOnly - name: Copier les APK signés vers un emplacement connu # Le build Android avec -r android-arm64 produit l'APK dans From e08c61ba4cbf5710c4f3f1c6c2c323348985f11b Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 01:09:49 +0100 Subject: [PATCH 88/95] restore a GitHub badge --- .forgejo/workflows/release.yml | 16 ++++++++-------- README.md | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index ba0f2c89..c62fda77 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -177,16 +177,16 @@ jobs: dotnet restore - name: Build de PostIt.Android ARM64 - run: | - cd /src/_src - dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release -r android-arm64 --no-restore -clp:ErrorsOnly + run: | + cd /src/_src + dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ + -c Release -r android-arm64 --no-restore -clp:ErrorsOnly - name: Build de PostIt.Android x64 - run: | - cd /src/_src - dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release -r android-x64 --no-restore -clp:ErrorsOnly + run: | + cd /src/_src + dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ + -c Release -r android-x64 --no-restore -clp:ErrorsOnly - name: Copier les APK signés vers un emplacement connu # Le build Android avec -r android-arm64 produit l'APK dans diff --git a/README.md b/README.md index d9e9dfa7..3f067ed3 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml -* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) +* [![Build and Push Yavsc Apk](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml) + +* [![Build and Push Yavsc Production Image](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml) # Documentation From ecca6c07b40c866b395534a861688de34337f72b Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 01:21:21 +0100 Subject: [PATCH 89/95] Simpler is better --- .forgejo/workflows/release.yml | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index c62fda77..a72f92bd 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -188,22 +188,6 @@ jobs: dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ -c Release -r android-x64 --no-restore -clp:ErrorsOnly - - name: Copier les APK signés vers un emplacement connu - # Le build Android avec -r android-arm64 produit l'APK dans - # bin/Release/net10.0-android/android-arm64/. On le copie à - # la racine du checkout pour que l'étape d'upload le trouve. - run: | - cd /src/_src - for MARCH in arm64 x64; do - APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-$MARCH/fr.pschneider.postit-Signed.apk - if [[ ! -f "$APK" ]]; then - echo "::error::APK not found at $APK" - ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android 2>/dev/null || true - exit 1 - fi - cp "$APK" /src/_src/PostIt.Android-$MARCH.apk - done - - name: Publier la release Forgejo via l'API REST # Pas d'action tierce (pas de Node dans l'image runner). # On parle à l'API Forgejo directement via curl. @@ -318,7 +302,7 @@ jobs: -H "Authorization: token $GITHUB_TOKEN" \ -H "Content-Type: application/octet-stream" \ -H "Accept: application/json" \ - --data-binary "@/src/_src/PostIt.Android-$MARCH.apk" \ + --data-binary "@/src/_src/src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-$MARCH/fr.pschneider.postit-Signed.apk" \ "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android-$MARCH.apk") echo "POST asset -> HTTP $HTTP" if [[ "$HTTP" != "201" ]]; then From ab8ab00ae8c4bae06f008ace21557672bca95c8e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 01:42:27 +0100 Subject: [PATCH 90/95] Release 1.0.8-rc5 --- .vscode/settings.json | 1 + CHANGELOG.md | 16 ++++------------ README.md | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1cb334e0..6eb4faad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,7 @@ "DOTNET", "ecdsa", "envsubst", + "Forgejo", "Hsts", "Newtonsoft", "Npgsql", diff --git a/CHANGELOG.md b/CHANGELOG.md index 31098958..0a921f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,17 +16,8 @@ Cette convention est partagée avec le dépôt [`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) pour la production des paquets `.deb`. -## [1.0.8-rc4] - unstable -### Added - -### Changed - -### Fixed - -* [TODO] bug loading a blog post from PostIt, ACL come along with and don't need any "Refresh" button. - -## [1.0.8-rc3] - unstable +## [1.0.8-rc5] - unstable ### Added @@ -41,8 +32,9 @@ L'identifiant de l'application client Android a changé, il passe en minuscules ### Fixed -nothing, [TODO] a bug persists laoding a blog post from PostIt, ACL sould come along with and should not need any "Refresh" button. -Today, assigning ACL succeeds the first time, the following times result in a 409 +a bug posting and retrieving ACL from the backend, +the ACL now comes along with the article, +[TODO][PostIt] keep ACL along with the article ## [1.0.8-rc1] - unstable diff --git a/README.md b/README.md index 3f067ed3..e504687c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml -* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) -* [![Build and Push Yavsc Apk](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml) +* [Obsolete (see above Forgejo actins) ![Build and Push Yavsc Apk](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml) * [![Build and Push Yavsc Production Image](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml) From 4a68494162b3a38e1609f86b69668175c1908915 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 01:52:11 +0100 Subject: [PATCH 91/95] updated --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index e504687c..33dc4c77 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,7 @@ https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml -* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) -* [Obsolete (see above Forgejo actins) ![Build and Push Yavsc Apk](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-android.yml) - -* [![Build and Push Yavsc Production Image](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions/workflows/docker-publish-backend.yml) - +* [![Build and Push Yavsc Production Image](https://forgejo.[![Build and Push Yavsc Production Image](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml) # Documentation L'architecture, la roadmap et les détails métier sont documentés From 5d1649df80bae261fe7fe9279e909652db10525c Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 02:41:59 +0100 Subject: [PATCH 92/95] dont boot twice the app --- .vscode/settings.json | 49 ++++++++++++++------------- src/PostIt/PostIt/App.axaml.cs | 22 ++++++++---- src/Yavsc.Api.Client/BlogApiClient.cs | 2 +- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6eb4faad..83a17ae3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,30 +2,31 @@ "dotnet-test-explorer.testProjectPath": "test/**/*Tests.csproj", "cSpell.words": [ - "appsettings", - "asciidoctor", - "ASPNETCORE", - "Avalonia", - "Configurabilité", - "Cratie", - "DESTDIR", - "dotnet", - "DOTNET", - "ecdsa", - "envsubst", - "Forgejo", - "Hsts", - "Newtonsoft", - "Npgsql", - "Oidc", - "PKCE", - "postit", - "pschneider", - "SLNDIR", - "validable", - "www-data", - "yavsc", - "Yavsc" + "appsettings", + "asciidoctor", + "ASPNETCORE", + "Avalonia", + "blogspot", + "Configurabilité", + "Cratie", + "DESTDIR", + "dotnet", + "DOTNET", + "ecdsa", + "envsubst", + "Forgejo", + "Hsts", + "Newtonsoft", + "Npgsql", + "Oidc", + "PKCE", + "postit", + "pschneider", + "SLNDIR", + "validable", + "www-data", + "yavsc", + "Yavsc" ], "cSpell.reportUnknownWords": true, "cSpell.language": "fr,en", diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index 7caf7bda..e3735c1e 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -15,6 +16,8 @@ namespace PostIt; public partial class App : Application { + private int _bootStarted; + /// /// DI container the platform entry points hand to ViewModels so /// they can resolve the canonical singleton @@ -45,8 +48,6 @@ public partial class App : Application { var window = ServiceProvider.GetRequiredService(); desktop.MainWindow = window; - var api = ServiceProvider!.GetRequiredService(); - desktop.MainWindow.Opened += async (_, _) => await BootAsync(this.ServiceProvider!, api); View = window.MainView; this.ConfigureRootView(window.MainView); @@ -75,10 +76,8 @@ public partial class App : Application private void ConfigureRootView(MainView rootView) { - var api = ServiceProvider!.GetRequiredService(); - - // Déclencher le Boot lors du chargement du contrôle à l'écran - rootView.AttachedToVisualTree += async (_, _) => await BootAsync(this.ServiceProvider!, api); + // Déclencher le Boot une seule fois lors du chargement du contrôle à l'écran. + rootView.AttachedToVisualTree += async (_, _) => await BootOnceAsync(); var sessionStatus = ServiceProvider!.GetRequiredService(); sessionStatus.LogoutCompleted += () => @@ -95,6 +94,17 @@ private void ConfigureRootView(MainView rootView) rootView.SessionBanner.DataContext = sessionStatus; } + private async Task BootOnceAsync() + { + if (Interlocked.Exchange(ref _bootStarted, 1) == 1) + { + return; + } + + var api = ServiceProvider!.GetRequiredService(); + await BootAsync(this.ServiceProvider!, api); + } + /// /// Test-only hook: bind a concrete so /// command-driven navigation paths () can diff --git a/src/Yavsc.Api.Client/BlogApiClient.cs b/src/Yavsc.Api.Client/BlogApiClient.cs index cbc82358..8284c5ed 100644 --- a/src/Yavsc.Api.Client/BlogApiClient.cs +++ b/src/Yavsc.Api.Client/BlogApiClient.cs @@ -33,7 +33,7 @@ namespace Yavsc.Api.Client; /// public sealed class BlogApiClient { - private const string DefaultPathPrefix = "blog"; + private const string DefaultPathPrefix = "blogspot"; private readonly IYavscApiClient _api; private readonly Uri _baseAddress; From 5b6d2e82db980307b82a98f9da9ab8e8fed5d9dc Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 02:43:03 +0100 Subject: [PATCH 93/95] Release 1.0.8-rc6 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a921f95..7210a227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ Cette convention est partagée avec le dépôt pour la production des paquets `.deb`. -## [1.0.8-rc5] - unstable +## [1.0.8-rc6] - unstable ### Added From 243c0f2780d15e9c07dbfbb944a5b9c903c9a8b2 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 04:00:10 +0100 Subject: [PATCH 94/95] Disposing the test fixtures --- .forgejo/workflows/buildAndTest.yml | 2 +- .../BlogApiMappedClaimsTests.cs | 7 +--- .../MappedClaimsBlogsWebServerFixture.cs | 4 +- src/Yavsc.Tests.Shared/IBackendFixture.cs | 4 +- src/Yavsc.Tests.Shared/WebHostFixture.cs | 37 ++++++++++--------- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 86694c89..cb18656d 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -44,4 +44,4 @@ jobs: - name: Test run: | echo "🚀 Lancement des tests..." - cd /src/_src && dotnet test --verbosity normal && echo "✅ Success !" + cd /src/_src && dotnet test --verbosity normal && echo "✅ Success !" || echo "❌ Fail ($?)!" diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs index f6b46ba9..00e2ea61 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs @@ -14,15 +14,10 @@ namespace Yavsc.Blogs.Tests; [Collection("JwtClaimMapping")] public sealed class BlogApiMappedClaimsTests : -IClassFixture, -IBackendFixture +IClassFixture { private readonly MappedClaimsBlogsWebServerFixture _fixture; - public IReadOnlyList Addresses => throw new NotImplementedException(); - - public IServiceProvider Services => throw new NotImplementedException(); - public BlogApiMappedClaimsTests(MappedClaimsBlogsWebServerFixture fixture) { _fixture = fixture; diff --git a/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs index 2546de0a..7311ce04 100644 --- a/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs @@ -25,7 +25,7 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix { private readonly InMemoryDatabaseRoot _inMemoryRoot = new(); private readonly Dictionary _savedInboundMap; - private readonly WebApplication _app; + private WebApplication? _app = null; public MappedClaimsBlogsWebServerFixture() { @@ -86,6 +86,7 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix public void Dispose() { + if (_app is null) return; _app.StopAsync().GetAwaiter().GetResult(); _app.DisposeAsync().AsTask().GetAwaiter().GetResult(); @@ -96,6 +97,7 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix } } + private sealed class NoopFileSystemAuthManager : IFileSystemAuthManager { public FileAccessRight GetFilePathAccess(System.Security.Claims.ClaimsPrincipal user, string fileRelativePath) diff --git a/src/Yavsc.Tests.Shared/IBackendFixture.cs b/src/Yavsc.Tests.Shared/IBackendFixture.cs index 4e2f009d..b5040b96 100644 --- a/src/Yavsc.Tests.Shared/IBackendFixture.cs +++ b/src/Yavsc.Tests.Shared/IBackendFixture.cs @@ -1,5 +1,4 @@ - -public interface IBackendFixture +public interface IBackendFixture : IDisposable { /// /// The addresses the fixture bound to. @@ -10,4 +9,5 @@ public interface IBackendFixture /// The service provider for the fixture host. /// IServiceProvider Services { get; } + } diff --git a/src/Yavsc.Tests.Shared/WebHostFixture.cs b/src/Yavsc.Tests.Shared/WebHostFixture.cs index 95b23082..f40da18d 100644 --- a/src/Yavsc.Tests.Shared/WebHostFixture.cs +++ b/src/Yavsc.Tests.Shared/WebHostFixture.cs @@ -30,14 +30,13 @@ namespace Yavsc.Tests.Shared; /// mapping are the responsibility of the subclass, through /// . /// -public abstract class WebHostFixture : IDisposable, IBackendFixture +public abstract class WebHostFixture : IBackendFixture { private static readonly Lazy _selfSignedCertificate = new Lazy(CreateSelfSignedCertificate); private static readonly object _sync = new object(); private static WebApplication? _app; private static bool _isInitialized; - private static int _instanceCount; private static readonly List _sharedAddresses = new(); private static IServiceProvider? _sharedServices; @@ -57,11 +56,12 @@ public abstract class WebHostFixture : IDisposable, IBackendFixture /// successfully and the host is running. public bool IsInitialized { get; private set; } +#pragma warning disable CS8618 // Un champ non-nullable doit contenir une valeur autre que Null lors de la fermeture du constructeur. Envisagez d’ajouter le modificateur « required » ou de déclarer le champ comme pouvant accepter la valeur Null. protected WebHostFixture() +#pragma warning restore CS8618 // Un champ non-nullable doit contenir une valeur autre que Null lors de la fermeture du constructeur. Envisagez d’ajouter le modificateur « required » ou de déclarer le champ comme pouvant accepter la valeur Null. { lock (_sync) { - _instanceCount++; if (!_isInitialized) { InitializeAsync().GetAwaiter().GetResult(); @@ -110,6 +110,8 @@ public abstract class WebHostFixture : IDisposable, IBackendFixture /// listen port.
protected virtual int HttpsPort => 5101; + public WebApplication App { get; private set; } + private async Task InitializeAsync() { var builder = WebApplication.CreateBuilder(); @@ -122,14 +124,14 @@ public abstract class WebHostFixture : IDisposable, IBackendFixture }); }); - var app = BuildApp(builder); - app = await ConfigurePipelineAsync(app); - await app.StartAsync(); + this.App = BuildApp(builder); + this.App = await ConfigurePipelineAsync(this.App); + await this.App.StartAsync(); - _app = app; - _sharedServices = app.Services; + _app = this.App; + _sharedServices = this.App.Services; - var server = app.Services.GetRequiredService(); + var server = this.App.Services.GetRequiredService(); var addressFeatures = server.Features.Get(); _sharedAddresses.Clear(); if (addressFeatures?.Addresses is not null) @@ -147,15 +149,14 @@ public abstract class WebHostFixture : IDisposable, IBackendFixture { lock (_sync) { - _instanceCount--; - if (_instanceCount == 0 && _app is not null) - { - _app.StopAsync().GetAwaiter().GetResult(); - _app = null; - _isInitialized = false; - _sharedAddresses.Clear(); - _sharedServices = null; - } + if (!IsInitialized) + throw new InvalidOperationException("Cannot tear down a fixture that has not been initialized."); + this.App.StopAsync().GetAwaiter().GetResult(); + IsInitialized = false; + + _isInitialized = false; + _sharedAddresses.Clear(); + _sharedServices = null; } } From 3b4cc241a7841ef4f7f2a4d1e2db944c603b698e Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 04:17:15 +0100 Subject: [PATCH 95/95] badges display --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 33dc4c77..b132f3f2 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,11 @@ https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml # Statut actuel des actions GitHub --* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) +* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) + +* [![Build and Push Yavsc Production Image](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml) + -* [![Build and Push Yavsc Production Image](https://forgejo.[![Build and Push Yavsc Production Image](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml) # Documentation L'architecture, la roadmap et les détails métier sont documentés