Client PostIt:
- JoinedRooms, SelectedRoom, messages et utilisateurs par salle
(FocusedMessages/FocusedUsers), OnRoomJoined alimente depuis la
valeur de retour du Join
- ChatPage : ComboBox de choix de la salle en focus, listes bindees
sur les vues de focus
Serveur:
- AddSignalR().AddJsonProtocol(IncludeFields=true) : ChatRoomInfo
n'expose que des champs publics, STJ ne les serialisait pas et le
client recevait des objets vides (JoinedRooms jamais alimente)
ChatUserNames n'etait jamais alimente (relique d'un SetUserName disparu),
ce qui faisait lever KeyNotFoundException a chaque connexion SignalR et
fermait immediatement la connexion cote client ("Déconnecté (The server
closed the connection...)" dans PostIt).
- IConnexionManager.OnConnected prend desormais le userName en parametre
- HubConnectionManager.OnConnected alimente ChatUserNames et ChatCxIds
- ChatHub.OnConnectedAsync passe le userName resolu depuis les claims
Inclut aussi les changements PostIt lies (bandeau de statut avec
commande d'action, handlers Reconnecting/Reconnected/Closed).
DeleteUserDirOrFile (and DeleteUserFile) read FileInfo.Length AFTER
fi.Delete(), and FileInfo.Length throws FileNotFoundException once
the file is gone. The controller's catch turned that into a 400, so
the user saw "le fichier n'existe pas sur le serveur" while the delete
had in fact succeeded. Capture the length before deleting.
Adds FileSystemApiTests.Delete_fs_removes_the_file_and_returns_200
(asserts 200 + file gone from disk). 8/8 FileSystemApiTests pass.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The custom ProfileService (Yavsc.Services.ProfileService) was never
registered with IdentityServer, so IS8 fell back to its default
profile service — which only emits claims listed in each API
resource's UserClaims (RequestedClaimTypes). "name" was never among
them, so every access token lacked "name" and GetUserName() resolved
to null on all bearer resource hosts (Blogs/Api): the MyFiles upload
500 and the unreachable User.Identity.Name. The class was dead code;
all prior tweaks to it (FullName→UserName) and to ServiceExtensions
(MapInboundClaims=false) were necessary but insufficient until it was
actually wired in.
- HostingExtensions: AddProfileService<ProfileService>() after
AddAspNetIdentity<ApplicationUser>().
- ProfileService: emit name=user.UserName, email, roles for any user
grant whose client has "profile" in AllowedScopes, independent of
the caller (the old ParsedScopes gate only fired for the id-token
caller, never the access-token caller).
- ServiceExtensions: JwtBearer NameClaimType=JwtClaimTypes.Name,
MapInboundClaims=false (keep short claim names as-emitted).
- FileSystemHelpers: EnsureDestinationDirectory uses GetUserName()
(the "name" claim) instead of User.Identity.Name for the per-user
storage root.
Tests:
- TestTokenIssuer: stamp a "name" claim = subject so test tokens mirror
production shape (fixes FileSystemApiTests upload 500).
- Remoting: PasswordToken_carries_name_claim_equal_to_login mints a
real JWT via the IS token endpoint (password grant, openid profile)
and asserts the access token carries name=login and GetUserName()
resolves to it.
- WebServerFixture: AccessTokenType=0 (Jwt in IS8; 1=Reference, the
inverse of Duende); allow openid+profile on the test client; re-seed
the openid/profile identity resources in ReseedAuthTestData (a
ResetAndMigrateDatabase wipes the shared in-memory store and previously
left them gone → invalid_scope).
- BillingServiceTests: [Collection("Yavsc Server")] to serialize with
the host build that mutates the static BillingService/Config
registries (pre-existing parallel race that the new test's timing
surfaced).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Espace de stockage personnel géré depuis PostIt, avec attachement par
référence (sans duplication) des fichiers à la demande du client
(NominativeServiceCommand) et au devis du fournisseur (Estimate).
Backend:
- UploadedFile.OwnerId (FK ApplicationUser, nullable) ; les téléversements
vers api/v1/fs créent/mettent à jour une ligne propriétaire.
- Tables de jointure EstimateAttachedFile et QueryAttachedFile (PK composite),
migration UserFileAttachments.
- EstimateApiController: GET/POST/DELETE estimate/{id}/attachments
(fournisseur uniquement, fichier devant appartenir au caller).
- FrontOfficeApiController: GET/POST/DELETE front/query/{id}/attachments
(client uniquement).
- FileSystemApiController: GET fs/file/{fileId} sert les octets depuis le
host Blogs (autorisation propriétaire ou partie au devis/demande via DB
partagée) — le host API ne gère que les métadonnées, contrainte des
conteneurs docker à disques distincts.
Client .NET:
- UserFilesApiClient (list/upload/delete/download) pointant vers BlogsApiUrl.
- DTOs UserDirectoryDto/FileEntryDto/AttachmentDto/FileReceivedDto/UploadFile.
- Méthodes d'attachement sur EstimateApiClient et FrontOfficeApiClient.
PostIt (Avalonia):
- Page « Mes fichiers » (naviguer/téléverser/télécharger/supprimer), servant
aussi de sélecteur pour l'attachement.
- Section pièces jointes dans l'édition du devis (fournisseur) et le détail
de la demande (client), avec ajout depuis My Files, détachement,
téléchargement.
- Enregistrement DI + mapping ViewLocator + bouton d'accès.
Tests:
- EstimateEditionPageViewModelTests mis à jour (nouveau ctor fsClient,
historique d'appels du stub pour les assertions post-envoi).
Nommage HTTP/méthodes en anglais, documentation en français.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- List removal: GetOngoingEstimatesAsProvider filters on
ProviderValidationDate so a provider's signed estimate leaves their
list; PostIt refreshes the list on navigation pop-back.
- Title renames: "Devis en attente de signature fournisseur/client"
on both the provider and client sides (HomePage, VM, tests).
- Move the estimate/bill TeX+PDF generation flow from Yavsc.Org into
Yavsc.Api (TeXHelpers, PdfGenerationViewModel, BillViewComponent,
Bill/Estimate templates, _ViewImports). Program.cs wires
AddControllersWithViews + SiteSettings; FrontOfficeApiController
injects IRazorViewEngine (cast to IViewEngine) with optional deps
and exposes estimate.tex/estimate.pdf endpoints keyed by query id.
- PDF generation drives through the Estimate_pdf view (side-effect
render) and compiles via the system lualatex, fed TeX on stdin (no
intermediate .tex), PDF to the bills dir, aux artifacts cleaned so
only the .pdf persists. Resolve the bills dir to an absolute path so
lualatex's -output-directory and working directory agree regardless
of the host cwd (fixes "I can't write on file estimate-1.log").
- PostIt: DownloadAsync on the client + GetEstimateTex/PdfAsync;
Télécharger PDF/TeX buttons on the validation and command-detail
pages via a StorageProvider save helper.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Reopening an already-validated estimate showed an empty pad: the
strokes were stored server-side but never sent back. Embed them in the
estimate payload and load them straight into the pad, in the same wire
format PostIt captured — no separate fetch.
- Server: Signature inverse navs get STJ [JsonIgnore] (cycle-safe
under System.Text.Json, matching ApplicationUser); the four estimate
read endpoints Include(e => e.Signatures) so strokes ride the payload
- Client: EstimateDto.Signatures with EstimateSignatureDto (Strokes,
Type=Pro/0|Client/1, CoordinateMax, CapturedAtUtc)
- PostIt: SignaturePadControl.Load(int[]) replaces the buffer and
raises RedrawRequested; EstimateValidationPageViewModel.LoadExisting
Signature() picks the perspective's side off the estimate and loads
it on Attach
- Tests: server asserts GET estimate/{id} carries the strokes; PostIt
VM tests cover load + side-by-perspective
Co-Authored-By: Claude Code <noreply@anthropic.com>
FrontOfficeApiController.AcceptQuery/RejectQuery now resolve the actor
(provider vs client vs admin) from the query's PerformerId/ClientId and
authorize accordingly — a non-party caller is forbidden. Either party may
accept or reject. Acceptance may carry a PostIt signature, which is staged
on the query's linked estimate (Estimate.CommandId == queryId) and sets the
estimate's ProviderValidationDate / ClientValidationDate; a signature with
no linked estimate, or a signature on a reject, returns 400. Admin bypasses
authz but cannot sign.
The shared signature-staging logic (find-or-add the Signature row by
(EstimateId, Type), write Strokes/CoordinateMax/CapturedAtUtc) is extracted
into EstimateSignaturePersister, and BillingController.Sign now calls it.
The persister owns no disk I/O and no SaveChanges — the caller persists in
its own transaction.
Signature.Strokes is now the sole source of truth: drop the FilePath
property, remove the on-disk EstimateSignatureFileHelper (and its tests),
and add the DropSignatureFilePath migration. The legacy PNG prosign/clisign
flow is independent of Signature.FilePath and is unchanged.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Cover the 60 commits since 1.0.8-rc14: blog attachments + Markdown
preview, the Estimate pipeline (list/edition/validation + API + EF
migrations), provider ongoing requests, file-ACL and genericEstimate
migrations, POSTIT_SETTINGS_JSON, billing filtered by user, shared
YavscMessageSender, Gitleaks CI, and the test stabilization wave.
Co-Authored-By: Claude <noreply@anthropic.com>