Commit graph

125 commits

Author SHA1 Message Date
5fe0d9eb45 Merge pull request 'release/1.0.7-rc1' (#36) from release/1.0.7-rc1 into release/1.0.7
Some checks failed
Dotnet build and test / log-the-inputs (pull_request) Successful in 10s
Dotnet build and test / build (pull_request) Successful in 6m45s
Forgejo Release / release (push) Failing after 29s
Reviewed-on: #36
2026-08-18 17:27:48 +01:00
15f018117f
chore(release): bump version via gitversion for 1.0.7-rc1 2026-08-18 17:20:59 +01:00
3fb5f40acb
feat(post): add Publish toggle for blog posts (no schema change)
Replaces the previous 'Visibility enum' approach (commit 33ecfa7e,
reverted in 42625f5d) with the existing BlogSpotPublication
mechanism. Paul pointed out that the system already had a
publication table and a Publish field on BlogPostEditViewModel;
we just didn't expose it through the API.

The toggle is its own action on the API surface — a dedicated
endpoint rather than a field on the existing BlogPost wire
DTO. This keeps the BlogPostDto contract unchanged and avoids
shoe-horning 'Publish' into the entity model alongside
Title/Article (where the existing BlogSpotService.Modify
already takes two overloads and a third felt like drift).

Server (Yavsc.Blogs / Yavsc.Server)
- PUT /api/BlogApi/{id}/publish  body { publish: bool }
  Returns 204 on success, 404 when the post doesn't exist,
  Challenge() (401) when the caller is not the author
  (EditPermission gate). Idempotent: PUT because the
  resulting state matches the body, not the request.
- BlogSpotService.SetPublishAsync(user, postId, publish)
  factored out of the existing
  Modify(BlogPostEditViewModel) inline toggle, so the new
  endpoint reuses the same BlogSpotPublication row logic
  (add row if missing on publish=true, remove row if
  present on publish=false).
- BlogPost.IsPublished (NotMapped) is now hydrated by the
  service after each Index/Details fetch — a single bulk
  lookup, not N+1 — and surfaces through the wire JSON
  so PostIt can show the current state without a follow-up
  request.
- ApplicationUser nav properties (Posts, Book,
  DeviceDeclaration, Connections, Circles, BlackList,
  Rooms, RoomAccess, Membership, BlogComments) now carry
  BOTH [JsonIgnore] (Newtonsoft) and
  [System.Text.Json.Serialization.JsonIgnore] so the
  Yavsc.Blogs test fixture (System.Text.Json) stops
  exploding on object cycles when serialising
  BlogPost.Author.Posts.Author.Posts. Production
  (Yavsc.Org, NewtonsoftJson) was already safe via the
  Newtonsoft-only attribute; this commit just makes the
  Yavsc.Blogs side consistent.

Client (Yavsc.Api.Client)
- BlogApiClient.SetPublishAsync(id, publish) → PUT to the
  new endpoint.

DTO wire (Yavsc.Abstract.Blogspot.BlogPost)
- BlogPostDto.IsPublished added. Same shape as the entity
  field; serialised as a plain bool in JSON.

UI (PostIt)
- MainPageViewModel.DraftIsPublished (ObservableProperty)
  mirrors the existing DraftTitle/DraftArticle pattern;
  hydrated from SelectedPost.IsPublished on selection
  change. TogglePublish command pushes the new state to
  SetPublishAsync and updates both the buffer and the
  selected post locally so the UI reflects the change
  without a full Refresh.
- MainPage.axaml: a CheckBox 'Publié' in the toolbar,
  bound to DraftIsPublished TwoWay and wired to
  TogglePublishCommand. The toggle is its own action
  (not part of Save), matching the wire contract.

Tests (Yavsc.Blogs.Tests)
- PublishEndpointTests (4 [Fact]):
    * PUT publish=true returns 204 and IsPublished is true
      in the next GET
    * PUT publish=false clears IsPublished
    * PUT on an unknown post returns 404
    * PUT by a non-author does not return 204 (Challenge)
- BlogsWebServerFixture now wires
  app.UseDeveloperExceptionPage() so 500s in tests
  surface a real stack trace instead of an empty
  InternalServerError body — much easier to diagnose
  future regressions.

Test totals: 24/24 Yavsc.Blogs.Tests (was 20, +4
PublishEndpoint), 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 'Publié' label is localised; the
  rest of MainPage.axaml is still hard-coded French.
- BlogPostEditViewModel.Publish ↔ IsPublished reconciliation
  in the admin web Yavsc (the Org UI already edits Publish
  inline; no work needed there).
2026-08-18 16:10:45 +01:00
42625f5ddd
Revert "feat(blog): add Visibility { Private, Public } to gate post reads"
This reverts commit 33ecfa7ebd.
2026-08-18 15:40:52 +01:00
33ecfa7ebd
feat(blog): add Visibility { Private, Public } to gate post reads
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).
2026-08-18 15:35:22 +01:00
1b289c1387
refactor(model): rename Yavsc.Blogspot.BlogPost to BlogPostDto
When commit 0e95e283 moved BlogPost from PostIt.Models to
Yavsc.Blogspot, it created an unfortunate collision with the
server-side EF entity Yavsc.Models.Blog.BlogPost. The two
classes have nothing in common beyond the name; the DTO is
the wire shape PostIt exchanges with the Blogs API, the EF
entity is the persistence model. Server code that imports both
namespaces (BlogSpotService.cs, etc.) ended up with 'BlogPost
is an ambiguous reference between X and Y' errors.

Renaming the client DTO to BlogPostDto (matching the
naming convention of the other DTOs in Yavsc.Api.Client.Dtos
— CircleDto, CircleAuthorizationDto, UserSearchResultDto)
disambiguates without renaming the EF entity on the server.

The namespace stays Yavsc.Blogspot; only the class name
changes. All call sites (client code, tests, XAML DataTemplates,
XML doc comments) are updated mechanically.
2026-08-18 00:20:01 +01:00
0e95e28327
refactor(model): move BlogPost DTO from PostIt.Models to Yavsc.Blogspot
BlogPost is shared between the server (Yavsc.Server/Models/Blog/
BlogPost.cs is the EF entity) and any client that talks to the
blogs API. Keeping the client-side DTO in PostIt.Models made
sense when there was only one consumer; now that the
Yavsc.Api.Client project is about to host BlogApiClient alongside
CircleApiClient and BlogAclApiClient, the DTO has to live in a
layer both the client project and PostIt can reference without
inverting the dependency.

Yavsc.Abstract is the existing home for cross-tier interfaces
and DTOs (IBlogPost, IBlogPostPayLoad, IApplicationUser).
Yavsc.Blogspot is the sub-namespace already used by the
matching interface, so the new concrete class follows.

Why not move Circle and CircleAuthorizationToBlogPost at the
same time? Both depend on the concrete ApplicationUser class
(via the Owner and Target/Allowed navigation properties) which
lives in Yavsc.Server. Moving them would mean either dragging
ApplicationUser into the abstract layer (huge blast radius —
auth, billing, chat, etc.) or weakening the navigation
properties (breaks EF Core shaping). They're staying where
they are; the new Yavsc.Api.Client will get DTO counterparts
instead.

Updated call sites:
- 4 .cs files: replace 'using PostIt.Models;' with
  'using Yavsc.Blogspot;' where the file was actually using
  BlogPost. Files that only used SignaturePadData keep their
  'using PostIt.Models;' — that type stays put.
- 1 .axaml file: xmlns:models="using:PostIt.Models" ->
  xmlns:models="using:Yavsc.Blogspot" (one DataTemplate for
  the post list in MainPage).

Build + tests green (51/51).
2026-08-17 23:45:45 +01:00
cd03b04755
re-refacto BlogPost serialization
Some checks failed
Dotnet build and test / build (push) Has been cancelled
Dotnet build and test / log-the-inputs (push) Has been cancelled
2026-08-05 21:11:22 +01:00
64547840e4
refacto BlogPost serialization
Some checks failed
Dotnet build and test / log-the-inputs (push) Has been cancelled
Dotnet build and test / build (push) Has been cancelled
2026-08-05 21:05:14 +01:00
b25e0e842e
refacto blogPost
Some checks failed
Dotnet build and test / log-the-inputs (push) Has been cancelled
Dotnet build and test / build (push) Has been cancelled
2026-08-03 01:36:12 +01:00
f3bb039d2f WIP audiences 2026-07-12 02:42:13 +01:00
Lum
bbdcc7f2ad Blog: render user avatar through a null-safe helper
GET /BlogSpot/Details/1 retournait 500 (avec un 500-sur-500 sur
la page d'erreur elle-même) parce que DisplayTemplates
/ApplicationUser.cshtml faisait `var avuri = "/Avatars/" +
Model.UserName + ".s.png"` : avec <Nullable>enable</Nullable>,
Razor émet un null-check implicite sur Model.UserName et lève
NullReferenceException quand l'auteur n'a pas de UserName
posé (donnée héritée, user partiellement initialisé).

- UserDisplayHelpers.AvatarSrc : helper statique pur dans
  Yavsc.Abstract.Identity qui retourne
  YavscConstants.DefaultAvatar pour user null / UserName vide
  ou whitespace, et un path /avatars/<name>.s.png sinon.
- ApplicationUser.cshtml : utilise le helper.
- Tests : 4 cas (null, vide, whitespace, valide) dans
  Yavsc.Org.Tests/NonRegression.

Bonus : le path d'avatar passe de "/Avatars/" (S majuscule,
ne résolvait pas dans le middleware de fichiers statiques) à
YavscConstants.AvatarsPath ("/avatars" minuscule), pour fermer
l'autre trou que centraliser le calcul permettait de fixer
proprement.
2026-07-11 19:41:09 +01:00
6ac264fa2c tests 2026-07-06 00:47:35 +01:00
7b3a236bcc refacto query status 2026-07-04 22:35:53 +01:00
ee6cb34c23 renaming Reviewed 2026-07-04 22:25:59 +01:00
90dfe9c13f drop the deigner.cs 2026-07-04 16:06:02 +01:00
1d26cbdf3d refacto chathub 2026-07-04 15:28:07 +01:00
0cc82fec5d bug fix IUserSettings 2026-06-30 23:55:43 +01:00
1751145be8 Exploiting GitVersion 2026-06-28 14:11:26 +01:00
c1f4d19975 fices the UI 2026-06-15 02:55:22 +01:00
bdc974b29b érrance 2026-06-12 12:47:04 +01:00
bba719c8c1 doc : Architecute 2026-06-12 12:34:30 +01:00
e98bba4247 refacts and PostIt Nav 2026-06-11 20:02:32 +01:00
80144bf9fc refacts, and launching Blogs 2026-06-04 22:04:20 +01:00
e24208e77b refact 2026-05-30 19:34:22 +01:00
1aafb7cc7d IModerationService 2026-05-30 18:20:17 +01:00
a90cdc08b8 docker composes 2026-05-30 15:41:59 +01:00
95dec3636a Use InMemory db driver at testing 2026-05-28 22:18:26 +01:00
3b4376a5f0 repo refs 2026-05-24 20:55:29 +01:00
5620213cf5 better 2026-04-19 20:36:03 +01:00
Paul Schneider
e042e34bf7 refact and login 2026-03-09 02:07:42 +00:00
Paul Schneider
cbd5822fa2 diable nullable 2026-03-01 17:25:25 +00:00
Paul Schneider
40e8e08690 reorg 2026-02-28 21:17:54 +00:00
Paul Schneider
45514010f2 REORG 2026-02-14 16:54:27 +00:00
Paul Schneider
bcadc733bb refacts 2025-11-16 15:53:13 +00:00
Paul Schneider
44cbaefa9c User list 2025-08-19 21:32:17 +01:00
Paul Schneider
997c39703a Authorizations 2025-08-18 11:27:13 +01:00
Paul Schneider
5c046dfa88 build cli 2025-07-16 00:47:50 +01:00
Paul Schneider
8fc1153e59 testing 2025-07-14 20:35:13 +01:00
Paul Schneider
253a2919f3 misc 2025-07-07 07:49:18 +01:00
Paul Schneider
21cd878839 fixies the refact of blog spot index 2025-06-29 19:53:56 +01:00
Paul Schneider
9b7a6c0125 refact 2025-06-29 18:44:35 +01:00
Paul Schneider
39788f46f9 migration net9.0 2025-06-16 02:30:32 +01:00
Paul Schneider
03b098f37e a description ? 2025-06-08 15:23:46 +01:00
Paul Schneider
5998c474af bump sdk to net-9 2025-06-08 14:41:50 +01:00
Paul Schneider
380f2314dc Code cleanup 2025-03-04 18:05:21 +00:00
Paul Schneider
b9ba97f5e8 User settings 2025-02-24 23:29:48 +00:00
Paul Schneider
71262647b3 permission handling 2025-02-23 20:23:34 +00:00
Paul Schneider
9e6a4f7d13 Get User Id from Web API 2025-02-17 23:57:12 +00:00
Paul Schneider
f2e54953c0 references cleanup 2025-02-16 18:43:32 +00:00