Commit graph

2,903 commits

Author SHA1 Message Date
4b4afa2eae docker-compose: explicit source/target mapping for build secrets
The shorthand 'secrets: - yavsc_appsettings' relies on Compose
v2 to derive both source and target from the same name. In
some BuildKit integrations this is not enough — the secret id
seen inside the Dockerfile (yavsc_appsettings) and the source
defined at top-level (yavsc_appsettings, file: ...) end up not
being mapped correctly, leading to:

  cp: cannot stat '/run/secrets/yavsc_appsettings':
    No such file or directory

at the blogs-runtime / api-runtime / web-runtime stages.

Use the explicit long form:

  secrets:
    - source: yavsc_appsettings
      target: yavsc_appsettings

in all three runtime services. source is the top-level secret
name (file: ./src/Yavsc.Org/appsettings-org.json); target is
the id BuildKit exposes inside the container at
/run/secrets/yavsc_appsettings, matching --mount=type=secret,
id=yavsc_appsettings in the Dockerfile.
2026-06-27 18:25:26 +01:00
831b5139c1 ci: pass --target build-env to docker build in APK workflow
After the Dockerfile refactor into multi-stage (commit 6f975f87),
'docker build .' without --target selects the LAST stage of the
Dockerfile — which is blogs-runtime, an ASP.NET image with no
APK to extract. The subsequent docker cp command then fails
with:

  Error: No such container:path: …PostIt.Android/bin/Release/
  net10.0-android/android-arm64/com.CompanyName.PostIt-Signed.apk

Add --target build-env to scope the build to the build-env stage
(the one that produces both the .apk and the publish artifacts,
and which still ends with CMD ["bash"]).
2026-06-27 18:21:42 +01:00
7069faa1fd roadmap: tick off conteneurisation item in Jalon 0
The 14 docker commits landed across this session achieve the
critère de sortie: 'docker compose up' starts db/api/blogs on
a bare host, and web fails with a documented IdentityServer
signing-certificate error that points at the installation
procedure (volume mount + Kestrel:Endpoints:Https in
appsettings-org.json).

Update the checkbox status and link to CONTRIBUTING.md so a
reader of ROADMAP.md can navigate to the install procedure.

Two Jalon 0 items remain open:
- ◐ NuGet centralisation (partial)
- ☐ Tests d'intégration smoke par BC
2026-06-27 18:17:18 +01:00
c9c71ec645 Merge remote-tracking branch 'github/dependabot/github_actions/all-actions-640176b5ab' 2026-06-27 18:13:33 +01:00
8ec32e7931 contributing+compose: clarify 'machine vierge' criterion for Jalon 0
Make explicit what was implicit: 'docker compose up' is not
expected to start everything on a bare host. Yavsc.Org (web)
requires an HTTPS signing certificate for IdentityServer8 in
Production mode, and the volume mount /etc/letsencrypt:/etc/letsencrypt:ro
is the documented way to supply it.

Two related changes:

- CONTRIBUTING.md, 'docker compose up' section: spell out that
  db + api + blogs start cleanly on a bare host, web fails with
  the documented IdentityServer error, and that the difference
  between 'vierge' and 'configured' is exactly the cert volume.
- docker-compose.yaml, web service: expand the commented volumes
  block to point at the same error message and reference the
  'HTTPS en production' section in CONTRIBUTING.md, so an
  operator reading the compose file knows what to uncomment
  and where to look.
2026-06-27 18:12:28 +01:00
234c837937 drop baked-in Kestrel:Endpoints:Https from Yavsc.Blogs / Yavsc.Api
Both appsettings-blogs.json and appsettings-api.json shipped a
Kestrel:Endpoints:Https block pointing at https://localhost:3003
and https://localhost:3004 respectively. These are leftover dev
templates that don't match the production ports (5004/5005 for
Blogs, 5002/5003 for Api) and crash Kestrel at startup when no
cert is configured for those URLs.

docker-compose.yaml now sets ASPNETCORE_URLS explicitly per
service runtime (http://+:5000/5002/5004 + ASPNETCORE_HTTPS_PORT=empty),
which is the authoritative source for the binding. The baked
Kestrel block in the appsettings overrides nothing useful and
just blocks HTTP-only startup.

Search confirms no source code references localhost:3003/3004
(grep across src/*.{cs,json,axaml,cshtml} returns only the two
lines we just deleted), so removing the block is safe.
2026-06-27 17:36:57 +01:00
332e30db27 docker-compose: force ASPNETCORE_ENVIRONMENT=Production on runtime
.env contains ASPNETCORE_ENVIRONMENT=Development, which gets
loaded via env_file into every service. With ASPNETCORE_ENVIRONMENT=Development, ASP.NET Core loads appsettings-org.Development.json — which has a Kestrel:Endpoints block binding BOTH http://localhost:5000 AND https://localhost:5001. The HTTPS endpoint has no cert on a fresh host, so Kestrel crashes with:

  fail: Microsoft.Extensions.Hosting.Internal.Host[11]
        Unable to configure HTTPS endpoint. No server certificate
        was specified, and the default developer certificate could
        not be found or is out of date.

Fix: add an explicit ASPNETCORE_ENVIRONMENT=Production to each
runtime service's environment: block. In Compose, literal
environment: entries override env_file: entries with the same
name, so this wins over .env's Development value.

Production-loaded appsettings-org.json has no Kestrel block, so
Kestrel uses only the ASPNETCORE_URLS env var we already set
(http://+:5000 etc.) — HTTP only, no HTTPS crash.
2026-06-27 17:29:34 +01:00
b320732ed4 contributing: document the HTTP-only env block and prod HTTPS recipe
Update the 'HTTPS en production' section to match the new
docker-compose layout:

- explain why the per-service environment: block pinning
  ASPNETCORE_URLS to HTTP-only and clearing ASPNETCORE_HTTPS_PORT
  is required (appsettings-org.Development.json sets
  Site.Authority to https://localhost:5001, which makes Kestrel
  auto-detect an HTTPS endpoint and crash without a cert);
- give the exact 5-step recipe to enable HTTPS in production:
  switch ASPNETCORE_URLS to a double-bind form, uncomment the
  HTTPS port, uncomment the /etc/letsencrypt volume mount, add a
  Kestrel:Endpoints:Https block in appsettings-org.json pointing
  at the Let's Encrypt fullchain.pem + privkey.pem, and rebuild
  the runtime image (since appsettings are baked via BuildKit
  secret mount).
2026-06-27 17:24:26 +01:00
78469021ed docker-compose: force ASPNETCORE_URLS=HTTP only in dev per service
The runtime services were trying to bind HTTPS even though the
compose file only mapped HTTP ports. Symptom on first start:

  fail: Microsoft.Extensions.Hosting.Internal.Host[11]
        Hosting failed to start
        System.InvalidOperationException: Unable to configure
        HTTPS endpoint. No server certificate was specified,
        and the default developer certificate could not be found
        or is out of date.

The root cause is appsettings-org.Development.json which sets
  Site.Authority = https://localhost:5001
combined with Kestrel's auto-detection of https_port from the
listening URL. ASP.NET Core 9 promotes any 'Authority' / 'Url'
property to a Kestrel binding target unless ASPNETCORE_URLS is
explicitly set.

Fix: add an environment: block on each runtime service pinning
ASPNETCORE_URLS to the HTTP-only form, plus empty
ASPNETCORE_HTTPS_PORT to suppress auto-detection. This forces
HTTP-only binding on the 'machine vierge' criterion of Jalon 0,
where no cert is available.

For production HTTPS, the existing commented # - '5001:5001' /
# volumes: /etc/letsencrypt blocks stay the way to enable it:
uncomment the port, uncomment the volume, and add a
Kestrel:Endpoints:Https block in appsettings-org.json that
points at the Let's Encrypt cert files.
2026-06-27 17:23:24 +01:00
bcfff12dab contributing: update containerisation section for multi-stage build
The previous section described three separate Dockerfile.runtime*
files, one per runtime service. After the multi-stage refactor
of Dockerfile (6f975f87) and the deletion of those three files
(4fc0ddb6), the section was stale.

Rewrite it to describe the new structure:

- one Dockerfile with multiple stages (build-env, publish-org /
  api / blogs, web-runtime / api-runtime / blogs-runtime);
- Dockerfile.backend kept for the production image workflow;
- the BUILD_ENV_TAG ARG that propagates the build-env image
  pin across the three locations where it has to be updated.

Update the 'Bumper l'image de build' section to point at the
new ARG location (was: a list of Dockerfile.runtime* files).
Update the isolated-build example to use --target web-runtime
instead of -f Dockerfile.runtime.

Also fix a structural regression introduced while editing: a
duplicate '## Conteneurisation' header and a missing
'## Sessions DDD' transition — both restored here.
2026-06-27 16:36:29 +01:00
4fc0ddb6d4 docker: use Dockerfile multi-stage targets in compose
After the multi-stage refactor of Dockerfile, the three separate
Dockerfile.runtime* files are obsolete: every runtime image is
now a stage of the main Dockerfile.

Delete Dockerfile.runtime, Dockerfile.runtime.blogs,
Dockerfile.runtime.api, and rewrite docker-compose.yaml so that
each service points at the corresponding target via build.target:

  web    -> web-runtime    (port 5000)
  api    -> api-runtime    (port 5002)
  blogs  -> blogs-runtime  (port 5004)

The build tag is passed through build.args.BUILD_ENV_TAG, which
the Dockerfile declares as an ARG with the same default as
before.

The shared multi-stage Dockerfile is now the single source of
truth for both the build-env image (used by the APK workflow)
and the three runtime images.
2026-06-27 16:32:48 +01:00
6f975f87af docker: refactor Dockerfile into multi-stage build
Rewrite Dockerfile as a single multi-stage file with seven
stages:

  build-env        : base image + restore + build + publish
                     (default target, keeps docker-publish-android
                     workflow functional — it just needs the APK)
  publish-org      : isolated copy of Yavsc.Org publish output
  publish-api      : same for Yavsc.Api
  publish-blogs    : same for Yavsc.Blogs
  web-runtime      : ASP.NET image for Yavsc.Org (port 5000)
  api-runtime      : ASP.NET image for Yavsc.Api (port 5002)
  blogs-runtime    : ASP.NET image for Yavsc.Blogs (port 5004)

Before this commit, the runtime images tried to COPY from an
external pazof/yavsc-build-env image that did not contain the
published artifacts (only built them, never published). The
multi-stage fix moves the publish step and the runtime copy
into the same Dockerfile, so COPY --from=publish-org etc.
reference local stages — no external artifact coupling.

The build-env tag is now exposed as ARG BUILD_ENV_TAG (default
debian12-dotnet10-android36-v1) so docker-compose can override
it via build.args without editing the Dockerfile.
2026-06-27 16:31:32 +01:00
ad1e4a6f4e contributing: document containerisation (compose, secrets, HTTPS bump)
Add a 'Conteneurisation' section covering:

- the three image families (build env, runtime per project);
- the yavsc-build-env pin on Docker Hub and the
  dotnet-android-build-image sibling repo;
- the docker compose up flow (4 services, healthcheck-gated);
- how appsettings-org.json is injected via BuildKit secret mount
  (file remains on the host, never lands in a layer);
- the HTTPS-in-prod recipe (uncomment ports 5001/5003/5005 +
  /etc/letsencrypt volume + Kestrel:Certificates in
  appsettings-org.json + ASPNETCORE_URLS override);
- the bump procedure when the build-env image is rebuilt
  (rebuild + push with new tag, then update Dockerfile,
  Dockerfile.backend, the three Dockerfile.runtime*, and
  docker-compose.yaml in lockstep);
- an isolated build/run check for one runtime image.

Also corrects an outdated mention of 'build.args.BUILD_ENV_IMAGE'
(removed in the previous commit) — the lockstep list now points
at the docker-compose 'build' blocks instead.
2026-06-27 16:28:05 +01:00
ab37c5403a docker: hardcode BUILD_ENV_IMAGE tag in COPY --from=
'COPY --from=${BUILD_ENV_IMAGE}' is rejected by BuildKit:
  failed to solve: failed to parse stage name "${BUILD_ENV_IMAGE}":
    invalid reference format: repository name
    (library/${BUILD_ENV_IMAGE}) must be lowercase

A COPY --from= can only reference either a local stage of the
same Dockerfile, or a static image reference. ARG interpolation
in the stage name is not supported.

Replace the ARG + interpolation with the pinned tag in all
three Dockerfile.runtime* files. Bumping the build-env image
now means updating Dockerfile, Dockerfile.backend, and the three
Dockerfile.runtime* in lockstep.
2026-06-27 16:26:39 +01:00
2d472779b0 drop Yavsc.Web references from Dockerfiles, ROADMAP, arch doc
Yavsc.Web is an empty template/draft project — no useful code,
conceptually a duplicate of the front that lives in Yavsc.Org.
Remove its mentions from:

- Dockerfile, Dockerfile.backend: the COPY src/Yavsc.Web/*.csproj
  step is useless (the project is referenced nowhere downstream)
- ROADMAP.md: the 'Perimetre technique' table no longer lists it
- doc/architecture/decoupage-organisation.md: removed from the
  ASCII diagram and the per-project table

Note: this commit only removes references; the empty project
itself (src/Yavsc.Web/) and the yavsc.sln Project() entry are
left in place for now. A future commit can rm -rf the directory
and prune the .sln when we're sure nothing else still depends
on it.
2026-06-27 16:24:27 +01:00
73f8f51186 docker-compose: point yavsc_appsettings secret at the real file path
The previous commit referenced ./appsettings-org.json at the repo
root, but the actual file lives at src/Yavsc.Org/appsettings-org.json
(next to its template appsettings-org-template.json).

Without this fix, 'docker compose up' fails with:
  failed to stat /home/paul/Workspace/yavsc/appsettings-org.json:
    stat ...: no such file or directory
2026-06-27 16:19:22 +01:00
49644e3858 docker-compose: full web + api + blogs + db with healthchecks
Rewrite docker-compose.yaml to match the runtime architecture
introduced in the previous commits:

- 4 services: db (postgres:16), web (Yavsc.Org), api (Yavsc.Api),
  blogs (Yavsc.Blogs). Each runtime service builds from its own
  Dockerfile.runtime* with a pinned BUILD_ENV_IMAGE.
- Healthcheck on db via pg_isready. web/api/blogs wait for
  service_healthy before starting (was: bare depends_on which
  races the DB on cold boot).
- Two named networks: yavsc-internal (db + runtimes) and
  yavsc-public (runtimes only). Compose v2 default, but the
  split makes the intent explicit and lets the operator
  externalise the public network if needed.
- Each runtime service injects appsettings-org.json via the
  yavsc_appsettings BuildKit secret (file: ./appsettings-org.json).
  No appsettings in the build context.
- HTTPS ports (5001, 5003, 5005) and the /etc/letsencrypt volume
  mount are commented out — uncomment them in production when
  Kestrel:Certificates is configured in appsettings-org.json.
- The old POSTGRES_* build args on the web service are gone: the
  build env no longer needs DB credentials (only the runtime does,
  via env_file).
2026-06-27 16:14:31 +01:00
7e99bbd54c docker: add Dockerfile.runtime / .blogs / .api for ASP.NET images
Three new Dockerfiles, each producing a minimal runtime image
based on mcr.microsoft.com/dotnet/aspnet:10.0:

- Dockerfile.runtime         : Yavsc.Org (port 5000 HTTP)
- Dockerfile.runtime.blogs   : Yavsc.Blogs (port 5004 HTTP)
- Dockerfile.runtime.api     : Yavsc.Api (port 5002 HTTP)

Each one:

1. COPY --from=pazof/yavsc-build-env:debian12-dotnet10-android36-v1
   /app/publish/<project>/ — i.e. the artifacts produced by the
   publish step added in the previous commit.
2. Injects appsettings-org.json via BuildKit secret mount
   (--mount=type=secret,id=yavsc_appsettings). The secret never
   lands in a layer — BuildKit copies it into /app and discards
   the mount.
3. Sets ASPNETCORE_URLS to the project's HTTP port and exposes it.
4. Adds a HEALTHCHECK that pings the root URL.

HTTPS (ports 5001, 5003, 5005) is intentionally NOT exposed in
the Dockerfile: enabling it requires mounting /etc/letsencrypt
(typically via docker-compose) and configuring Kestrel:Certificates
in appsettings-org.json. The compose file in the next commit
documents the volume mount pattern.
2026-06-27 16:13:12 +01:00
d068ff4061 docker: publish Yavsc.Org / Api / Blogs artifacts to /app/publish
Add a 'dotnet publish' step at the end of both Dockerfiles so the
runtime images (Dockerfile.runtime / .blogs / .api) can COPY the
published output via --from=build-env.

Before this commit, the Dockerfiles only ran 'dotnet build' and
ended with CMD ["bash"] — i.e. they were pure build-env images
with no consumable artifact for runtime use.

Dockerfile publishes Yavsc.Org, Yavsc.Api and Yavsc.Blogs (used
by docker-publish-android.yml which extracts the APK).
Dockerfile.backend publishes Yavsc.Org only (used by
docker-publish-backend.yml for the pazof/yavsc production image).

Appsettings are NOT published: they are supplied at runtime via
BuildKit --mount=type=secret (see Dockerfile.runtime in the next
commits) or via a docker-compose volume.
2026-06-27 16:12:16 +01:00
c780ddfb07 docker: pin build-env image to a versioned tag
Replace ':latest' with ':debian12-dotnet10-android36-v1' in
both Dockerfiles so the build is reproducible. The build-env
image is constructed from /home/paul/Workspace/dotnet-android-
build-image and pushed to Docker Hub under that tag.

When the build-env image is rebuilt (new dotnet SDK, new
Android SDK, etc.), bump this tag and rebuild the image locally
before re-running the workflows.

The two Dockerfiles serve different GitHub Actions workflows:

- Dockerfile: builds everything including PostIt.Android;
  used by .github/workflows/docker-publish-android.yml to
  extract the signed APK.
- Dockerfile.backend: builds only Yavsc.Org; used by
  .github/workflows/docker-publish-backend.yml to produce the
  pazof/yavsc production image.
2026-06-27 15:58:29 +01:00
85de25c2ab roadmap: tick off two Jalon 0 items now documented
- 'Decoupage Yavsc.Org vs Yavsc.Server vs Yavsc.Api clarifie
     dans l'Architecture'
  - 'CONTRIBUTING.md'

Both landed in commits 91bd97c3 / 4ba1aa88 (page) and 7b24b481
(CONTRIBUTING). Update the checkbox status and link to the
artefacts so a reader of ROADMAP.md can navigate directly to
them.

Three items remain in Jalon 0: NuGet centralisation (partial),
containerisation, and BC smoke tests.
2026-06-27 15:57:41 +01:00
4ba1aa8858 doc: correct Yavsc.Blogs role (backend, not front)
When documenting the per-project layout in the previous commit,
I described Yavsc.Blogs as 'Sous-domaine front web specifique
au blog', which is wrong: Yavsc.Blogs contains only ApiController
classes, services and models — no Razor views. The plan is to
deploy it as a headless backend API on a dedicated subdomain in
production, while the blog front (Razor views) stays in
Yavsc.Org to share rendering and auth.

Correct the description, the ASCII diagram, the table row, and
the 'why' paragraph accordingly.
2026-06-27 15:05:24 +01:00
7b24b4812d add CONTRIBUTING.md
Covers the last missing piece of Jalon 0 in ROADMAP.md:

  'CONTRIBUTING.md (build, tests, conventions, DDD sessions)'

Sections:
- prerequisites (.NET 10, PostgreSQL, Node for Avalonia Browser,
  Android SDK for PostIt.Android)
- first build + how to start Yavsc.Org in Development
- how to run the test suite
- code conventions (delegated to .editorconfig + a few extras)
- branches and commit messages (trunk-based, scoped imperatives)
- link to architecture/decoupage-organisation.md for the
  per-project layout
- DDD sessions (link to doc/ddd-exploration-*.md + ROADMAP.md)
- security reminders (no secrets in git, user-secrets / env vars)
- pointer to GitHub issues + new DDD sessions for design Qs
2026-06-27 14:52:52 +01:00
91bd97c3fd doc: add architecture/decoupage-organisation.md
Documents the per-project layout under src/ (Abstract, Server,
Org, Api, Blogs, Web, Org.Tests) as one of the two remaining
items of Jalon 0 in ROADMAP.md:

  'Decoupage Yavsc.Org vs Yavsc.Server vs Yavsc.Api clarifie
   dans l'Architecture'

The page is referenced from the new doc/README.md index, and
will also be linked from CONTRIBUTING.md in the next commit.
2026-06-27 14:52:28 +01:00
989cca669f Nettoyage de la dette documentaire 2026-06-27 14:34:49 +01:00
532d42cec5 readme: add Documentation section pointing to doc/README.md index
Insert a short 'Documentation' section between the GitHub Actions
status badges and 'Construction et deploiement'. The new section
names the doc/ directory, links to doc/README.md (the new index),
and to Architecture.md (the architecture root).
2026-06-27 14:25:28 +01:00
c573d117d0 doc: add doc/README.md index of architecture / roadmap / dev docs
Lists the documents under doc/ in four sections so a reader can
find their way to the right page without grepping the tree:

- Architecture & design: Architecture.md (root) + the seven
  per-topic pages under doc/architecture/.
- Roadmap & design exploration: ROADMAP.md (at the repo root)
  and doc/ddd-exploration-2026-06-14.md.
- Samples: doc/offer-sample.md.
- Work journal: doc/dev-tracking/* (informal, not published).

Each entry has a one-line description so the table is scannable
on its own.
2026-06-27 14:25:08 +01:00
cde2eb174a readme: clarify centralised collection / no payroll split
The two limitations (centralised PayPal collection, no payroll)
were grammatically fine but mixed model statement and technical
implementation in a way that obscured each.

Split into two bullets, each with a bold topic label:
- 'Collecte centralisée' names the fact (single PayPal account,
  third-party status) and the technical reason (only credentials
  configured).
- 'Pas de gestion de paie' restates the unit-payment-only limit
  in present tense.
2026-06-27 14:18:54 +01:00
aa9ce40b12 readme: shorten 'no ticketing / no complex projects' bullet
Original phrasing ('Elle ne prendra pas en charge, du moins pas
encore, ni … ni …') was wordy and used a future tense ('ne
prendra pas') for a limitation that is already observable in
the current codebase.

Keep the semantic content unchanged (no claim about roadmap
status) — this commit only restates the existing limitation in
present tense and removes the 'du moins pas encore' hedge.
2026-06-27 14:18:31 +01:00
15aa4a4dd8 readme: tighten PayPal-only payment bullet
Code review: grep for Stripe|Adyen|Braintree|PSP across src/
returns zero hits. PayPal is the only payment service provider,
and the integration is built on SetExpressCheckout (NVP/SOAP),
not the PayPal REST API PayPal has been recommending since 2017.

Rewrite the bullet as:
- explicit naming of the only PSP and the absence of any other;
- clarification that the deprecation is PayPal's, not ours;
- honest statement that no migration is planned.
2026-06-27 14:16:23 +01:00
6025be865f readme: rewrite post-prestation claim bullet
Code review confirms there is no application-level handling for
post-prestation claims: grep for Reclamation|Litige|Complaint|
Dispute across src/ returns zero hits. No route, no controller,
no model. The only mention of conciliation is in the design
exploration doc, scheduled for Jalon 5.

Replace 'toute reclamation necessitera l'intervention d'un
systeme auxiliaire (un processus humain?)' with an explicit
statement of the absence, the Jalon 5 target, and a link to
doc/ddd-exploration-2026-06-14.md where the design lives.
2026-06-27 14:14:16 +01:00
5707162edd readme: rewrite cancellation bullet to match actual code
Code review of HairCutCommandController confirms the previous
bullet ('Dans le cas de l'avance ... aucune annulation de la
prestation n'est supportée') understated the gap:

- Only the client side has any surface area
  (ClientCancel GET + ClientCancelConfirm POST).
- ClientCancelConfirm does _context.HairCutQueries.Remove(query)
  followed by SaveChangesAsync — no PayPal refund, no logic
  distinguishing arrhes vs avance.
- There is no PerformerCancel / ProviderCancel action anywhere
  in the codebase (grep -rn PerformerCancel|ProviderCancel
  returns nothing).
- No Refund* call exists anywhere in src/ (grep -rn Refund
  returns nothing).

The README's earlier promises (arrhes +20% on provider cancel,
arrhes lost on client cancel, advance non-cancellable) were
never implemented. PayPal flow has never been end-to-end tested.

Replace with a two-clause statement: current state (partial,
no refund, untested), target (RefundTransaction wired + full
workflow), link to ROADMAP.md.
2026-06-27 14:12:06 +01:00
79aedc302b readme: rewrite 'one command, one prestation' limitation
The original bullet ('à une commande, une prestation') was
syntactically broken (ellipsis without a verb, doubled 'à') and
said nothing about the multi-party target.

Replace with a two-clause statement that names both the current
limitation and the planned target, with a link to ROADMAP.md
where the multi-party direction is documented.

Bullet now reads:
  Aujourd'hui : une prestation par commande, sur un axe
  client → prestataire unique, sans sous-traitance.
  Cible roadmap : montages multi-parties (plusieurs clients
  et/ou plusieurs fournisseurs collaborant autour d'un même
  projet, avec sous-traitance validée par le client) —
  voir la ROADMAP.
2026-06-27 14:06:40 +01:00
575b623abb readme: rewrite authentication paragraph to remove ambiguity
The original phrasing 'Ni le client ni le prestataire ne sont
anonymes pour l'application, ils sont même formellement
authentifiés, au moment de leur accord pour une première
facturation en ligne, à l'occasion' was contradictory: the
paragraph asserted the users are not anonymous, then anchored
the formal authentication to the moment of the first billed
act, which a careful reader could parse as 'they ARE anonymous
until then'.

Rewrite as two distinct statements:
- both parties are nominatively identified and authenticated
  from the moment they register;
- a stronger verification step is triggered at the first
  billable act (light KYC on the client side, professional
  profile validation on the provider side).

While here, fix a small grammar slip in the second bullet
('de la validation' -> 'lors de la validation').
2026-06-27 14:00:45 +01:00
942b642fdc readme: split bullet and drop inline TODO in Limitations
The 'professionals are third parties' bullet was carrying an
inline 'TODO Aucune edition de fiche de paye …' mid-sentence,
which is hard to scan and mixes two distinct concerns
(commissioning / payout). Split into two bullets and tidy the
francais:
- 'edition' -> 'édition'
- 'payments unitaires' -> 'paiements unitaires'
- 'Seul … le sont' -> 'Seuls … le sont' (subject agreement)
2026-06-27 13:58:56 +01:00
c49a04384d readme: fix typo 'stokés' -> 'stockés' 2026-06-27 13:58:43 +01:00
dbafd225d0 readme: fix typo 'profile' -> 'profil' 2026-06-27 13:58:28 +01:00
be7d4f4ff9 readme: fix typos in authentification paragraph
- 'il sont' -> 'ils sont' (subject agreement)
- 'authentifies' -> 'authentifiés' (missing accents)
2026-06-27 13:58:21 +01:00
d25db13035 readme: fix typo in H3 title (Déploient -> Déploiement) 2026-06-27 13:58:13 +01:00
74de6aa1d1 doc: split Architecture.md into per-topic pages
doc/Architecture.md was 436 lines and growing; this commit
extracts each non-trivial subject into its own page under
doc/architecture/ and reduces the root document to a table of
contents + transversal sections (vision, stack, admin rights).

New pages (under doc/architecture/):

- workflow-multi-parties.md : client / fournisseur /
  coordinateur roles, sous-traitance, project states, B2B/B2C,
  domaine musical production flow.
- domaine-musical.md : titres collaboratifs (formats, flux de
  production, contraintes de licence).
- licences.md : LicenceModele, CC/ODbL seed, badge projet,
  cycle de vie.
- domaines-activite.md : arbre des activites, Droit a la
  racine, DomaineActivite model.
- dictionnaires-metier.md : regle d'heritage, DictionnaireMetier
  + TermeMetier, cycle de vie d'un terme. Absorbs the previous
  doc/Dictionnaire.md draft.
- offres-frontmatter.md : ClasseFormulaire / ClasseDevis,
  OffreFournisseur, Demande, parsing YamlDotNet (introduit dans
  4034c399 Front matters). Absorbs the previous
  doc/Formulaires-devis.md and doc/Demande.md fragments.
- postit-oidc.md : documentation du client desktop PostIt,
  custom URI scheme (RFC 8252 §7.1), composants partages,
  plateformes, UX observable, persistance et reprise au boot,
  garanties testees.

Architecture.md (436 -> 66 lines) keeps the vision, the stack
overview, the admin rights section, and a TOC table pointing at
each detail page. The "A documenter ensuite" backlog is kept
at the end.

Cross-links are relative: from Architecture.md the links go
architecture/<page>.md; from inside doc/architecture/ they go
../Architecture.md or <sibling>.md.
2026-06-27 13:45:31 +01:00
4034c39905 Front matters 2026-06-27 13:30:28 +01:00
514549c5f9 postit: traceable OIDC login UX + session persistence + 2nd-instance early exit
- OidcLoginPhase enum + IProgress<OidcLoginPhase> on
  YavscApiClient.LoginInteractiveAsync, surfaced in the UI as
  PhaseLabel (FR). Lets operators see where the flow actually
  stalls, in particular whether the postit://callback ever arrives
  on the running instance.
- YavscApiClient.TrySilentLoginAsync: silent refresh at boot.
  Returns false (and purges the store) when the refresh token is
  rejected by the OP.
- App.OnFrameworkInitializationCompleted auto-routes: HomePage is
  the navigation root; on Opened the app calls TrySilentLoginAsync
  and pushes MainPage if a session is restored. Logout pops back
  to HomePage via the new persistent SessionStatusBanner (Connecté
  / Déconnecté + Logout button).
- PostIt.Desktop.Program.Main now detects the postit://callback
  URL BEFORE Avalonia boots, hands it off via SingleInstance, and
  exits. Stops the 2nd PostIt instance from flashing its own
  MainWindow while the 1st instance is still waiting on the named
  pipe. The check in App.OnFrameworkInitializationCompleted is
  kept as belt-and-braces defence-in-depth.
- SchemeUrlDetector: pure platform-independent detector extracted
  for unit testing.
- Tests: 7 new SchemeUrlDetectorTests + 5 new phase / silent
  refresh tests in YavscApiClientTests.
2026-06-27 12:47:52 +01:00
ee2c8452ac Navigation and DI 2026-06-26 01:45:21 +01:00
e526b050ed Add the missing Redirect.cshtml view used by LoadingPage helper
AccountController.Signin (and ExternalController / ConsentController)
return this.LoadingPage("Redirect", model.ReturnUrl) when the OIDC
client is a native one (e.g. PostIt, with a custom-scheme redirect
URI). The LoadingPage extension in Yavsc.Extensions renders
controller.View("Redirect", …), so a /Views/Shared/Redirect.cshtml
must exist.

The file was missing, and the absence surfaced as a 500 on
POST /signin once the login itself succeeded — the user authenticated
fine, Identity.Application signed in, but the response body never
rendered and the POST returned InvalidOperationException
('The view Redirect was not found'). This is what broke the PostIt
flow after the seed/IdentityResource fixes landed.

The view is the standard IdentityServer quickstart loading page: a
meta-refresh that redirects the embedded browser to the OIDC
client's callback URI (postit://callback). Localizer strings are
used so the page is translatable like the rest of the auth UI.
2026-06-26 00:21:58 +01:00
847557318f code cleanup 2026-06-25 23:55:52 +01:00
8fdd56d33a Test the seed 2026-06-25 23:27:47 +01:00
571977f81b Fix LINQ translation in EnsureDefaultApplicationScopes
EF Core was throwing at startup with:

  System.InvalidOperationException: The LINQ expression
  '[ApiResourceScopeSpecification,...].Any(s => s.ResourceName == r.Name)'
  could not be translated.

The cause: Constants.ApiResourcesScopes is a static readonly C# array,
not an IQueryable, but it was used directly inside a Where clause on an
IQueryable<ApiResource>. EF tried to translate the closure over
Constants.ApiResourcesScopes into a SQL sub-query, which is not a
supported operation.

Materialise the wanted resource names into a HashSet before letting EF
see the Where — the collection is small (5 entries) so there's no
performance reason to push it down. After this fix,
EnsureDefaultApplicationScopes runs to completion at startup and
the seed actually has a chance of doing its job (assuming the rows
aren't already present).
2026-06-25 23:20:43 +01:00
68eb24ba44 Don't seed openid/profile/offline_access as ApiScopes
IdentityServer8 refuses to start when an IdentityResource and an
ApiScope share the same Name — it throws

  Found identity scopes and API scopes that use the same names.
  This is an invalid configuration. Scopes found: openid, profile

and the host crashes before serving any request.

Constants.BuildInApiScopes has historically listed 'openid',
'profile' and 'offline_access' alongside the application scopes
(admin, moderation, performer, client). The IdentityResource
counterparts are seeded separately via
IdentityResources.OpenId().ToEntity() /
IdentityResources.Profile().ToEntity() in
EnsureDefaultApplicationScopes, so listing them again in
BuildInApiScopes produces a duplicate 'openid' / 'profile' once
that seeder is wired into MigrateDatabase and starts running on
every restart (commit be334a69). 'offline_access' is handled
directly by IdentityServer8 (DefaultResourceValidator has a
special-case branch for it) and never needs an ApiScope row.

Trim BuildInApiScopes to application scopes only. The live
ConfigurationDb already contains both IdentityResources and
ApiScopes for the same names from earlier hand-rolled SQL
bootstrap, so the duplicate-name check fires the moment the
process tries to enumerate its resources at startup.
2026-06-25 23:11:16 +01:00
bbfaa039de Set explicit defaults on seeded ApiScopes and ApiResources
The previous commit (be334a69) relied on C# defaults to populate
the Postgres NOT NULL columns Enabled, Required, Emphasize,
ShowInDiscoveryDocument (on ApiScopes) and Created (on
ApiResources). Both tables declare these columns NOT NULL without
a database default, so EF Core ends up shipping C# defaults
(false / DateTime.MinValue) that violate the constraints or
silently disable the seeded rows.

Concretely, if we deployed be334a69 as-is:

- ApiScopes.Enabled = false -> the scope is invisible to
  DefaultResourceValidator, exactly the bug we're fixing.
- ApiResources.Created = DateTime.MinValue (0001-01-01) ->
  Postgres rejects the INSERT with
  'null value in column Created violates not-null constraint'.

Set the values explicitly so the seeder produces the same state
whether it runs once or a hundred times, fresh database or not.
2026-06-25 21:30:56 +01:00
be334a69dc Seed ApiResources + ApiResourceScopes, run seeder on every startup
The previous commit (37440171) added ApiScope rows for the
application scopes (admin, moderation, performer, client, blogs).
It was a partial fix: an ApiScope alone is not a valid scope from
DefaultResourceValidator's point of view. The validator only
recognises a scope if it can find an ApiResource that exposes it
(via ApiResourceScopes). Without that link, /connect/authorize
rejects the request with 'Scope X not found in store', even
though the scope row exists. This is what killed the PostIt login
in production.

This commit:

1. Extends Constants.ApiResourcesScopes with ResourceName +
   ResourceDisplayName. Topology: one ApiResource per scope
   ('admin' resource exposes 'admin' scope, 'blogs' resource
   exposes 'blogs' scope, etc.) — keeps each scope's audience
   specific if/when we split products across separate audiences.

2. Ensures EnsureDefaultApplicationScopes also inserts the
   matching ApiResource rows (deduped on Name) and ApiResourceScope
   rows linking each resource to its scope. Idempotent: missing
   rows are added, nothing is removed.

3. Removes the b.UseSeeding(...) call inside AddConfigurationStore.
   EF Core's UseSeeding callback only fires when the database is
   empty, so on a live ConfigurationDb (which already had Clients
   and ClientScopes) it never ran — that is why the previous commit
   had no visible effect on production. The seeder is now invoked
   explicitly from MigrateDatabase via SeedConfigurationDatabase,
   which resolves ConfigurationDbContext from the DI and runs
   EnsureDefaultConfiguration on every startup, regardless of
   whether the database was fresh.

   Seeding failures are caught and logged (best-effort) so a
   misconfigured seeder cannot prevent the host from booting.

Live data on yavsc.pschneider.fr is still missing the
ApiResource/ApiResourceScope rows; a one-shot SQL or a redeploy
with this commit is needed before PostIt can log in. Production
fix to follow.
2026-06-25 21:28:13 +01:00