release/1.0.7 #34
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "release/1.0.7"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds .forgejo/workflows/release.yml: triggered by tag push or workflow_dispatch, it validates the tag/CHANGELOG parity (stable / preview / unstable), builds the PostIt Android APK via the existing Dockerfile (--target build-env), and publishes a Forgejo release with the APK as an asset via rasterstate/forgejo-release-action@v1. Mirrors the validate-release logic of .github/workflows/docker-publish-android.yml so the two channels (Forgejo source-of-truth + GitHub mirror) stay consistent. Authentication uses ${{ secrets.RELEASE_TOKEN }}, a Forgejo PAT scoped to write:repository configured in the repository's Actions secrets.Repo-level secrets creation is broken on this Forgejo instance (InsertEncryptedSecret fails with UTF-8 byte-sequence error, likely a text-vs-bytea column type on the secret table). The fix is in upstream Forgejo v16; until then, ${{ secrets.GITHUB_TOKEN }} (auto- provided by the runner, scoped to contents: write for the current repo) keeps the release workflow operational without any UI setup. When the instance is upgraded and the secret table is migrated, revert this commit to switch back to ${{ secrets.RELEASE_TOKEN }} for least-privilege.The runner's docker label points at pazof/yavsc-build-env, a Debian image without Node.js. Any action like actions/checkout@v7, actions/upload-artifact@v7, rasterstate/forgejo-release-action, etc. fails at container start with 'executable file not found in /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/paul/.dotnet/tools:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:/home/paul/.nvm/versions/node/v22.23.0/bin:/home/paul/.local/bin:/home/paul/.npm-global/bin:/home/paul/bin:/home/paul/.nix-profile/bin'. This workflow is rewritten in pure bash: - replace actions/checkout with explicit git clone + checkout (full history + tags so GitVersion.MsBuild is happy); - merge the two jobs into one (no inter-job artifacts needed since everything shares the runner's filesystem); - replace rasterstate/forgejo-release-action with direct calls to the Forgejo REST API (/api/v1/repos/.../releases, .../assets), with python3 used to build and parse JSON bodies (jq not guaranteed in the runner image). Auth: ${{ secrets.GITHUB_TOKEN }} (runner-provided). The rasterstate action or any other Node-based action can be reinstated later if the runner image is swapped for one with Node installed.L'image runner pazof/yavsc-build-env n'a pas python3 (ni jq, ni node). Le step de publication Forgejo utilisait python3 pour générer les bodies JSON (POST /releases, PATCH /releases/{id}) et pour extraire le 'id' de la réponse. Fix : deux fonctions bash : - json_escape : escaping JSON des chaînes (\\, \", \n, \r, \t) - json_field : extraction d'un champ scalaire d'un fichier JSON via sed Suffisant pour les bodies qu'on envoie (tag_name, name, body, prerelease) et les champs qu'on lit (id).L'image runner pazof/yavsc-build-env installe jq (>= 1.7) à partir de debian12-dotnet10-android36-v2 (Dockerfile du repo dotnet-android-build-image, commit e06f096 "adds jq"). On en profite pour supprimer json_escape et json_field à base de sed, qui étaient fragiles : * sed est greedy par défaut : sur du JSON minifié d'une seule ligne (ce que renvoie l'API Forgejo de cette instance pour /releases/tags/<tag>), la regex s/.*"id".../\1/p attrape la DERNIÈRE occurrence de "id":<digits> sur la ligne, qui est l'id de l'auteur de la release (1, premier user du repo), pas l'id de la release (10706). * Le head -3 ajouté en PR #30 ne tient pas sur du JSON minifié : il n'isole rien et le sed greedy continue à capturer l'id de l'auteur. * PATCH /releases/1 tombait alors en 404 "The target couldn't be found" (cf. run échoué du 2026-08-17 04:05 sur le tag 1.0.6). jq résout les deux problèmes en une fois : * jq -r '.id' retourne le champ id racine, pas l'id imbriqué dans author. * jq -n --arg body "$RELEASE_BODY" '{body: $body, prerelease: $prerelease}' construit un body JSON proprement échappé (backslashes, guillemets, newlines, caractères de contrôle Unicode) sans avoir à le reproduire à la main. Effet de bord : les bodies PATCH et POST sont écrits dans /tmp/patch.json et /tmp/post.json puis passés à curl via --data-binary @<file> au lieu d'une variable shell. Plus de problème de quoting en chaîne shell, plus de collision avec les espaces ou les caractères spéciaux du body. Pré-requis côté runner : image pazof/yavsc-build-env:debian12- dotnet10-android36-v2 (avec jq) + maj du label correspondant dans la config du runner Forgejo.Round-trip out a long-standing hole in the ACL feature: until this commit a circle on Yavsc was an empty named bucket. You could create 'Famille' and grant it on a post, but the circle carried no members, so 'Famille' authorised no one. The MVC admin controller (Yavsc.Org.Controllers.CircleMembersController) existed but had no REST counterpart, so PostIt — which only talks to the Yavsc.Blogs API — had no way to manage membership at all. This commit closes that gap end-to-end: Server (Yavsc.Blogs) - GET /api/circle/{id}/members list members - POST /api/circle/{id}/members add a user (body { userId }) - DELETE /api/circle/{id}/members/{userId} remove a user All three are scoped to caller == circle.OwnerId; non-owned circles return 404 (not 403) to avoid leaking existence, in line with the rest of the controller. - Two new DTOs (CircleMemberDto, AddCircleMemberDto) for the wire shapes. CircleMemberDto mirrors UserSearchResultDto minus Email — membership UI doesn't need contact details. Tests (Yavsc.Blogs.Tests) - CircleMembersApiTests: 5 [Fact] covering empty list, add+get, duplicate add → 409, remove, and cross-owner 404. Test users (alice, bob) are seeded directly through the in-memory DbContext — the Blogs fixture doesn't stand up UserManager<ApplicationUser>. Client (Yavsc.Api.Client) - CircleMemberDto + 3 methods on CircleApiClient: GetMembersAsync, AddMemberAsync, RemoveMemberAsync. All match the server's contract: 404 flattens to null, 409 surfaces as an exception (callers can dedupe beforehand if they want idempotent behaviour). UI (PostIt) - CirclesPageViewModel gains a Members ObservableCollection that auto-loads on SelectedCircle change (via the partial setter generated by [ObservableProperty]). Commands: LoadMembersAsync, OpenAddMember (raises an event the view subscribes to), OnAddMemberConfirmedAsync (called by the view when the dialog confirms a selection), RemoveMemberAsync. 409 (already a member) is detected from the exception message and surfaced as a friendly status rather than an error — a likely race when the same user gets added twice through two UI paths. - CirclesPage layout is now two-pane (circles + editor on the left, members of the selected circle on the right). The member pane has an 'Ajouter un membre' button that opens AddCircleMemberDialog. Code-behind wires the dialog's Confirmed event back into the VM via an async lambda wrapper (EventHandler<T> wants void, the VM method is async Task). - AddCircleMemberDialog is a ContentPage (light modal, same pattern as PostAclDialog). Its ViewModel consumes IUserDirectory — the abstraction introduced by04a31709to fix the 'user search should not be IContactService' confusion. The dialog raises Confirmed with the picked UserSummary; the host (CirclesPage) is responsible for calling CircleApiClient.AddMemberAsync. Out of scope (tracked in MEMORY.md, 2026-08-18): - i18n: all visible text still hard-coded French. - XAML accessibility audit of pre-existing pages. - Avalonia.Headless UI tests of the new navigation flow. Tests: 51/51 PostIt.Tests green, 20/20 Yavsc.Blogs.Tests green (was 15; +5 for CircleMembersApiTests), 44/44 Yavsc.Org.Tests green (no regression).Replace the implicit 'ACL empty = private' convention with an explicit two-axis model: Visibility is the master switch, the ACL is the exception list. Semantics (matches what BlogSpotService.Index / Details enforce): Visibility.Public + empty ACL : every caller sees it Visibility.Public + non-empty : only author + ACL circles + admin Visibility.Private + any ACL : only author + admin (ACL ignored) ACL is preserved across Private/Public flips so reopening is lossless The Public+non-empty shape is the 'restrict by exception' case: open by default, narrowed by the ACL. This is intentionally different from the previous behaviour, where a Public post with a non-empty ACL was effectively ACL-restricted anyway — the new model makes that explicit and removes ambiguity. Server (Yavsc.Blogs / Yavsc.Server) - New enum Visibility { Private, Public } in Yavsc.Abstract.Blogspot (so the wire DTO and the EF entity share the same type). Stored as int via .HasConversion<int>() on BlogPost.Visibility. Default Private on construction; the column default in the migration is 0 so existing rows land Private without any data migration. - BlogSpotService.Index: filter rewritten to honour the two- axis model. Authenticated and anonymous callers now share the same shape (Public+emptyACL visible to all, otherwise scoped). Admin reads still go through PermissionHandler. - PermissionHandler.IsPublic: dropped the blogSpotPublications lookup, replaced with the Visibility + empty-ACL check that matches the new model. PermissionHandler.IsSponsor and IsOwner unchanged. - UserHelpers.UserPosts (the per-author feed for /CircleMembers/Details and similar): mirror of the Index filter, so the two code paths can't silently diverge. - BlogPostEditViewModel.Publish untouched on this commit. It still controls whether a row exists in BlogSpotPublication; the two systems coexist (Publish = 'is this draft published', Visibility = 'who can read it'). Follow-up to consolidate. EF migration (Yavsc.Org/Migrations/20260818143013_AddBlogPostVisibility) - Scaffolded by 'dotnet ef migrations add', not hand-edited, per the repo preference for generated migrations. - Adds the new Visibility column (int, NOT NULL, default 0). - Also drops three shadow-state ClientId1 foreign keys and their indexes/columns on ClientScopes, ClientRedirectUris, ClientGrantTypes. These shadow FKs were created by EF from HasOne<Client>().HasForeignKey(e => e.ClientId) mappings that have long since been removed from ApplicationDbContext.OnModelCreating, but the snapshot was never regenerated against the current model. The columns are nullable ints with no production data, so the drop is lossless. Without this, EF Core would keep emitting warnings on every migration add and the model would drift further from reality. DTO wire (Yavsc.Abstract.Blogspot.BlogPost) - Visibility property added to BlogPostDto. System.Text.Json serialises the enum as its underlying int, so the JSON shape is a plain number, no JsonConverter needed. Client UI (PostIt) - MainPageViewModel: DraftVisibility ObservableProperty mirroring the existing DraftTitle/DraftArticle pattern. Initialised to Private so a fresh draft is private by default. Save command writes the chosen value into the BlogPostDto payload for both CreatePostAsync and UpdatePostAsync. OnSelectedPostChanged hydrates the buffer from the server-supplied value. - AllVisibilities property on the VM exposes [Private, Public] in that order, bound by the ComboBox in MainPage.axaml. - VisibilityLabelConverter (PostIt.Views) maps the enum to French user-facing labels ('Privé' / 'Public'); registered in App.axaml as a static resource. - MainPage.axaml: a new ComboBox row in the editor pane between Title and Article. Uses the existing 'no hardcoded Background without Foreground' lesson so dark mode works. Tests (Yavsc.Blogs.Tests) - BlogVisibilityTests (5 [Fact]): drive GET /api/v1/blog with Visibility fixtures seeded directly in the in-memory DB: * Private + ACL: only the author sees it * Public + empty ACL: any authenticated caller sees it * Public + non-empty ACL: caller without ACL membership does NOT see it * Private + ACL: ACL is ignored, only the author sees it * Visibility round-trips through the JSON wire (int 1) - UserHelpersVisibilityTests (4 [Fact]): exercise the helper directly so the two code paths (Index filter vs per-author feed) can't diverge silently. Same fixture, no HTTP. Test totals: 29/29 Yavsc.Blogs.Tests (was 20, +5 BlogVisibility +4 UserHelpersVisibility), 51/51 PostIt.Tests (no change), 44/44 Yavsc.Org.Tests (no change). Out of scope (tracked in MEMORY.md, 2026-08-18): - i18n: only the new 'Visibilité :' label is localised; the rest of MainPage.axaml is still hard-coded French. - BlogPostEditViewModel.Publish ↔ Visibility consolidation (which system wins when both are set on the same post?). - Org-side UI for editing Visibility (the admin web Yavsc still edits posts without a visibility field).