Commit graph yavsc/src/Yavsc.Server/Services
Author SHA1 Message Date
92aa68c03c code reorg 2026-09-07 10:30:45 +01:00
e1d9bb38a3 Release 1.0.8-rc13
All checks were successful
Forgejo Release / release (push) Successful in 6m21s
2026-09-06 18:02:30 +01:00
0ba44c21e7 server: enable nullable annotations in legacy files 2026-09-06 16:11:37 +01:00
9e0908e126 load the ACL 2026-08-30 19:37:55 +01:00
14fc76e2a6 ixes invalid registration email formats are still rejected before the user is created
SMTP recipient rejections are logged and treated as a soft failure instead of crashing the request
2026-08-29 17:21:40 +01:00
447bbe49dc /bin/bash: ligne 1: q : commande introuvable 2026-08-29 15:37:13 +01:00
14dcd31da1 ✅ Persistance de SearchText ajoutée 2026-08-29 14:34:43 +01:00
b679707b83 using clauses cleanup 2026-08-22 03:34:48 +01:00
ec901e1f10 refacto API prefix + nav.back 2026-08-20 20:50:52 +01:00
b95dc3a800 refacto BlogPost 2026-08-19 14:19:45 +01:00
1840f920ad feat(post): add Publish toggle for blog posts (no schema change)
Replaces the previous 'Visibility enum' approach (commit 44dcbdd8,
reverted in 252327d8) 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
252327d88c Revert "feat(blog): add Visibility { Private, Public } to gate post reads"
This reverts commit 44dcbdd82e.
2026-08-18 15:40:52 +01:00
44dcbdd82e 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
633a305c35 Activity protection 2026-08-10 18:12:59 +01:00
ec0085b7e3 refacto blogPost 2026-08-03 01:36:12 +01:00
ce075b3aee Le créateur vient de l'authentification, donc on ne le prend pas du post 2026-08-02 23:02:54 +01:00
135003a913 Identity reloaded 2026-07-05 23:56:10 +01:00
8f8ee6383f refacto FrontmatterParser 2026-07-04 22:58:29 +01:00
5e4d548708 refacto query status 2026-07-04 22:35:53 +01:00
4034c39905 Front matters 2026-06-27 13:30:28 +01:00
2dec799d71 Introduce Yavsc.Interfaces.ISmtpClient and a recording test fake
Narrow ISmtpClient to the four operations MailSender actually uses,
behind a Yavsc.Interfaces.ISmtpClientFactory. Production wires
MailKitSmtpClient (SmtpClientFactory); tests wire a recording fake
(RecordingSmtpClientFactory). The fake is pre-registered in
WebServerFixture so SMTP calls are short-circuited; the EMailling
test now asserts the Connect -> Authenticate -> Send -> Disconnect
sequence. Yavsc.Abstract stays free of MailKit/MimeKit.
2026-06-22 02:09:02 +01:00
4398715004 reinstall fixes and reorg 2026-06-19 21:13:17 +01:00
dcf2a93ad0 Split Site:Audience into Site:ExternalUrl + Site:CorsAllowedOrigins
The Site:Audience setting was conflating two distinct concepts: an OAuth
JWT audience (a single resource identifier) and a CORS allow-list (an
array of origins). Collapsing them caused several latent bugs:
- OAuth/JWT validation expected a single string while CORS WithOrigins
  accepts an array.
- Password-reset callback URLs and OAuth client RedirectUri/Origin were
  being built from what was meant to be an audience identifier, not a
  base URL.
- Yavsc.Org's main CORS policy was hardcoded to '*', with no way to
  restrict it without code changes.

Changes:
- SiteSettings.Audience (string) replaced with CorsAllowedOrigins
  (IList<string>).
- OAuth JWT Authority still reads Site:Authority; Audience now reads
  Site:ExternalUrl (Org only; Api/Blogs use ValidateAudience=false).
- MailSender and AccountController build reset-callback URLs from
  Site:ExternalUrl.
- ClientController uses Site:ExternalUrl for OAuth RedirectUri/Origin
  defaults on newly created clients.
- Yavsc.Api and Yavsc.Blogs now read CORS origins from
  Site:CorsAllowedOrigins instead of hardcoded URLs.

Add shared AddYavscCors / AddYavscJwtBearer extension methods in
Yavsc.Server/Helpers/ServiceExtensions.cs to enforce a single
configuration contract across all runtime services (Api, Blogs, Org).
Fails closed when CorsAllowedOrigins is empty; fails fast at startup
when Site:Authority is missing.

Remove obsolete ConfigurationHelpers.GetAudience (no remaining callers).

Local appsettings-*.json files (which carry deployment-specific values
and are gitignored) must be updated to add Site:CorsAllowedOrigins.
2026-06-19 13:15:21 +01:00
e24208e77b refact 2026-05-30 19:34:22 +01:00
1aafb7cc7d IModerationService 2026-05-30 18:20:17 +01:00
6cc0c519d2 tests OK 2026-04-20 00:35:51 +01:00
87d62791b8 fix: test infrastructure with in-memory DB, SMTP mocking, and thread-safe billing configuration
- Add in-memory database support for test isolation in WebServerFixture
- Implement TestMailSender fake SMTP provider for email test support
- Add thread synchronization to billing service registration to prevent race conditions
- Make RegisterBilling<T> idempotent to safely handle reconfiguration
- Configure test environment via in-memory settings (UseTestEmailSender, UseInMemoryDatabase)
- Add regression tests for billing module idempotency and duplicate registration detection
- Fix tests: EMaillingTests.SendEMailSynchrone, BillingServiceTests (2 tests), HaveConfigurationRoot (3 tests)

All core test infrastructure tests now passing.
2026-04-19 14:40:40 +01:00
Paul Schneider
bfb37bc214 Blog Files model 2026-03-01 02:13:26 +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
714b2e0efb Passwor re-init 2025-09-07 17:43:13 +01:00
Paul Schneider
aa2ad71cc6 localisation 2025-08-31 18:27:53 +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
b3d565b6d9 Refactoring
The main server owns the migrations, it's the server part,
it's simpler.

It's Yavsc, not one of its lib.
2025-07-15 20:17:39 +01:00
Paul Schneider
20b6efd26c MEF 2025-07-10 08:38:38 +01:00
Paul Schneider
c51f11c7dc Blog post create 2025-07-09 13:16:01 +01:00
Paul Schneider
253a2919f3 misc 2025-07-07 07:49:18 +01:00
Paul Schneider
9b7a6c0125 refact 2025-06-29 18:44:35 +01:00
Paul Schneider
e794c22f4e refactoring 2025-06-29 16:12:16 +01:00
Paul Schneider
5998c474af bump sdk to net-9 2025-06-08 14:41:50 +01:00
Paul Schneider
050b8a7aa4 Track vscode setup 2025-03-02 16:53:56 +00:00
Paul Schneider
2bc0ce7a18 share the workflow config 2025-02-26 18:59:08 +00:00
Paul Schneider
b9ba97f5e8 User settings 2025-02-24 23:29:48 +00:00
Paul Schneider
76af07e271 no usage of DefaultProfileService 2025-02-15 21:15:12 +00:00
Paul Schneider
29303d5aea custom ProfileService 2025-02-15 20:06:07 +00:00
Paul Schneider
0936afb9ad TO FINISH ; Profile service ... 2025-02-15 18:40:22 +00:00
Paul Schneider
25b261f4ee more services from Web API 2025-02-15 12:45:27 +00:00
Paul Schneider
8e8f4d3896 refactoring 2025-02-14 22:44:33 +00:00
Paul Schneider
4b52b6e96e Got a login 2023-04-06 09:20:22 +01:00