46 Commits

Author SHA1 Message Date
Thomas Fransolet
269b3f6703 Lot C : backfill des colonnes de stockage (C2) et quota autoritaire (C3)
C2 — POST /api/Resource/backfill-storage, SuperAdmin, dryRun à true par
défaut : la migration se joue sur une base vide, ce backfill sur des lignes
de production. StoragePath par ResourceStorage.PathFor, SizeBytes par HEAD.

La méthode annoncée au plan — « SizeBytes par listing du bucket Firebase » —
était inapplicable : le serveur n'avait aucun client de stockage. Le sondage
passe donc par HEAD sur l'URL publique, comme le fait déjà la migration, et
le sondeur est extrait plutôt que recopié (Helpers/ResourceSizeProbe,
consommé par MigrationController et par le backfill). Même raisonnement que
pour ResourceStorage : deux copies auraient divergé sur ce qui compte, le
sort réservé aux échecs.

L'extraction a bouché un trou que personne ne cherchait. L'original ne notait
l'échec que dans son catch, or un HEAD sur un blob absent ne lève pas : il
répond 404, sans Content-Length. Ces ressources arrivaient à 0 octet sans
figurer dans le rapport — invisibles au quota et invisibles au diagnostic,
exactement ce que le commentaire d'origine voulait empêcher.

Le « 37 lignes sur 45 » du plan n'étant pas vérifiable, le backfill rend son
propre inventaire : Orphans (aucune URL, blob peut-être jamais téléversé) et
Unsized (URL présente, bucket muet) restent séparés, ce sont deux causes
distinctes.

C3 — pré-vol du quota sur les deux chemins de création, suppression du blob
à Delete, angle mort d'Update tranché.

Deux défauts trouvés en câblant, qui n'étaient documentés nulle part :

- Le pré-vol existait déjà à moitié. Upload (multipart) contrôlait et
  renvoyait 413, Create (JSON) ne contrôlait rien — or c'est le chemin
  qu'emprunte manager-app, qui crée la ligne puis téléverse.
- Les deux lectures du quota divergeaient. Upload lisait le quota du plan,
  GetQuota celui de l'instance avec le plan en repli. Une instance à quota
  surchargé — le mécanisme même de l'add-on — affichait un chiffre à l'écran
  et se faisait bloquer sur un autre. Helpers/StorageQuota devient la seule
  source de vérité pour les deux.

Delete supprime le blob AVANT la ligne et renvoie 502 en conservant la ligne
si le bucket échoue. manager-app faisait l'inverse en avalant l'échec dans un
print : la ligne disparaissait, le blob restait, et n'ayant plus de ligne il
devenait invisible au quota tout en restant facturé. Une ressource encore
listée se rattrape ; un blob que plus aucune ligne ne désigne, non.

L'angle mort laissé ouvert par C1 était une fausse crainte : PathFor ne
construit qu'un pictures/{instanceId}/{resourceId}, le type n'entre pas dans
le chemin, il décide seulement s'il y en a un. Recalculer ne peut donc pas
pointer ailleurs, et Update rejoue Apply.

Aucun secret nouveau : FirebaseAdmin était déjà référencé pour les
notifications push et Startup charge déjà un service account, donc
Google.Cloud.Storage.V1 réutilise le même GoogleCredential. Seule s'ajoute la
clé Firebase:StorageBucket, vide par défaut — à renseigner en prod (I9),
sans quoi Delete ne supprime rien et ne prétend pas le contraire.

dotnet build vert, dotnet test 163/163 (148 au départ, +7 pour C2, +8 pour C3).

Contient aussi le correctif d'indexation préparé en parallèle : un job
Hangfire par section dans BackfillInstanceAsync au lieu d'une boucle, et un
backoff sur 429/503 dans GoogleEmbeddingService.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 12:05:25 +02:00
Thomas Fransolet
7625487806 Lot B : geler le schéma, plus sécurité rapide et calculateur de stockage
LOT B — une seule migration EF (LotB_FreezeSchema) :
- SectionMap.MapResourceId → IconResourceId. L'écart (g) de la bascule tombe
  avec. Il fallait renommer aussi la propriété de navigation MapResource :
  la convention EF l'appariait au FK, la laisser aurait fabriqué un FK
  fantôme. Elle n'était utilisée nulle part ailleurs.
- SectionEvent.ParcoursIds supprimé (champ, DTO, SectionFactory, et une
  initialisation dans un montage de test).
- Instance.IsImageWatermark remplace le `instanceId == "633ee379…"` en dur
  de ResourceController.

EF a généré un RenameColumn, pas un drop+add : les icônes déjà configurées
survivent. L'avertissement de perte de données ne porte que sur le DropColumn
de ParcoursIds, ce qui est l'intention.

Non fait, et c'était une erreur de doc : « supprimer SectionEvent.IconResourceId ».
Ce champ n'existe pas — la ligne visée appartient à la classe imbriquée
MapAnnotation, partagée par SectionEvent, SectionAgenda et SectionMap, lue par
cinq contrôleurs et par GetReferencedResourceIds. La supprimer aurait cassé
les icônes d'annotation des trois types et la collecte offline.

SÉCURITÉ (lot A, même repo) :
- AuthenticationController.Authenticate : un bloc #if DEBUG écrasait l'email
  et le mot de passe reçus par un compte de test, donc toute compilation en
  Debug authentifiait n'importe quelle saisie. Retiré.
- EnableSensitiveDataLogging (qui écrit les valeurs des paramètres dans les
  logs) passe sous #if DEBUG, l'idiome déjà employé dans Startup.cs pour le
  CORS et Hangfire. Le Dockerfile publiant en -c Release, c'est un verrou réel.

LOT C1 :
- Calculateur StoragePath/SizeBytes extrait dans Helpers/ResourceStorage.cs,
  avec 13 tests fixant l'invariant des types URL. Il ferme le lien L5 : le
  backfill (C2) et l'écart (e) de la migration appelleront le même code.
- L'extraction a révélé la divergence qu'elle devait empêcher : des deux
  chemins de création de ResourceController, le chemin multipart écrivait
  SizeBytes mais laissait StoragePath nul.

dotnet build Debug et Release verts, dotnet test 143/143 (130 + 13).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:32:19 +02:00
Thomas Fransolet
18e4240f0f RAG: pipeline d'ingestion, endpoints du guide IA, journalisation RGPD
Ingestion et indexation
- IIngestionService/IngestionService : chargement des collections filles par
  sous-type, un jeu de morceaux par langue, ChunkIndex continu.
- SectionIndexingInterceptor retenu comme unique déclencheur : les 5
  sous-contrôleurs totalisaient 30 SaveChanges et 0 Enqueue, donc ajouter des
  points d'intérêt à une carte ne réindexait rien.
- HTML retiré avant l'embedding et lignes trop longues recoupées : sans cela un
  article dépassait l'entrée max du modèle et emportait son lot de 50 morceaux.
- Gabarits de LanguageInit filtrés, DistinctBy(Text) avant le Take : ils
  occupaient les cinq premiers résultats d'une recherche en néerlandais.

Endpoints du guide IA
- GET /api/Ai/knowledge/{id} : agrégats sur ContentEmbedding, donc sur ce qui
  est réellement indexé — compter les sections publiées serait plus flatteur et faux.
- GET /api/Ai/insights/{id} : miroir de GuideIaInsights côté manager-app, c'est
  l'écran qui a fixé la forme pour que le job de thèmes la remplisse.

RGPD
- VisitorQuestion journalisée dans AiController.Chat. HasAnswer se déduit des
  sources du retrieval, pas du texte : un repli poli ressemble à une réponse.
  L'écriture n'échoue jamais la réponse au visiteur.
- VisitorQuestionPurgeService, 90 jours, actif sans condition de configuration :
  une durée écrite dans les CGU n'est pas un réglage commercial.

Corrections
- Updateinstance ne recopiait pas les quotas du nouveau plan.
- CheckQuota ne bloquait ni ne comptait à quota 0 — IA gratuite non comptée.
- StoragePath et SizeBytes renseignés à Create, types URL exclus.

dotnet build 0 erreur, dotnet test 130/130.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:48:10 +02:00
Thomas Fransolet
af6b5b76ef Self-service onboarding (Stripe + Resend) + Postgres v3 schema (pgvector, media, visitor questions)
75 fichiers, ~1 mois de travail depuis f222861 (17/07). Contenu :

Onboarding self-service — jamais exécuté de bout en bout
  OnboardingController, StripeWebhookController, StripeService, ResendEmailService
  + IEmailService (10 templates), EmailTemplates/, TrialLifecycleService (Hangfire),
  PasswordTokenHelper, SlugHelper, 5 DTOs. Essai 14 j, Stripe customer/Checkout/Tax,
  mot de passe oublié + invitation user, plafond IA d'essai.

Schéma Postgres v3 — passe pendant que la base est vide
  ContentEmbedding + index HNSW (vector_cosine_ops), IEmbeddingService +
  GoogleEmbeddingService (gemini-embedding-001, 768 dims), Deployment/Dockerfile.postgres
  (postgis 3.4.3 + pgvector 0.8.6, épinglé par digest — un tag mobile rejouerait le
  warning de collation glibc). Colonnes Resource : StoragePath, FileName,
  IncludeInAiKnowledge, AiIndexStatus + nouveaux ResourceType ajoutés EN FIN d'enum.

Guide IA
  Champs Guide* sur Instance + InstanceDTO, AssistantService lit la configuration client
  dans les 4 blocs de prompt (ton codé en dur retiré, règle hors-sujet ajoutée aux deux
  variantes qui n'en avaient pas), IHttpClientFactory à la place des new HttpClient().
  Table VisitorQuestion + ConversationId sur AiChatRequest.

Stats
  Rétention unifiée à 13 mois (instances ET plans), VisitEventPurgeService.

Nettoyage
  IsStepLocked / IsHiddenInitially / FactContent supprimés de GuidedStep — IsStepLocked
  rendait une étape définitivement infranchissable même après réussite.
  SectionMap allégé (-57 lignes).

Tests
  SectionParcoursControllerTests, FakeConfiguration, FakeEmailService.
  ContentEmbedding a cassé 116 tests sur 124 (EF InMemory ne connaît pas Vector) :
  l'entité est exclue quand le provider n'est pas Npgsql. Conséquence assumée —
  le vector store n'est couvert par aucun test. dotnet test 124/124.

10 migrations EF. Base locale à jour, dotnet build 0 erreur.
Rien n'est en prod : la bascule Mongo → Postgres est décrite dans DOCS/STATUS.md §1quinquies.

⚠️ appsettings.json contient les clés Stripe (test) et Resend (prod) en clair — à rotationner.
2026-08-09 22:11:48 +02:00
Thomas Fransolet
f2228617ca Add instance index + date update audit + ai quota to token 2026-07-17 15:21:51 +02:00
Thomas Fransolet
3cd5f7573d misc (ia assistant, SectionParcours + others fixs) 2026-07-08 17:00:43 +02:00
Thomas Fransolet
674655eb80 Update agenda sync (image management) + update isStatistics to hasStats (subscription plan) 2026-05-07 22:00:42 +02:00
Thomas Fransolet
9633ae43b7 Add support for web + misc 2026-05-07 16:49:56 +02:00
Thomas Fransolet
e0ff0eeba6 Quota update in controller, audit log (serilog) + ai translator + Unit tests ! (to be tested) + migration update 2026-04-10 16:28:33 +02:00
Thomas Fransolet
09b7c75dac WeatherSync service (to test) 2026-03-25 17:38:44 +01:00
Thomas Fransolet
d5353eea9c wip service, fix some assistant ia bug, wip section agenda update, section event annotation etc 2026-03-25 10:56:54 +01:00
Thomas Fransolet
bad25bf5b3 Add push notification 2026-03-17 09:10:56 +01:00
Thomas Fransolet
a452f4af04 Add migration, need to check if migration with apikey correct.. ! + add roles and security (role and apikey) - need to be tested + visitevent stats + ai etc + all need to be tested ! 2026-03-13 17:45:01 +01:00
Thomas Fransolet
bde1666b42 Update save geometry + other, id null etc (thx claude) 2026-03-06 15:22:02 +01:00
Thomas Fransolet
f17d72b34e Multiple fixs after help from ai 2026-03-04 16:36:13 +01:00
Thomas Fransolet
94443fa411 Update puzzle to game + misc 2025-11-27 16:19:00 +01:00
Thomas Fransolet
62e302a0f0 Update code for event, agenda and map controllers -> added DTOs + misc 2025-07-16 14:57:13 +02:00
Thomas Fransolet
75a6b0c323 fix puzzle + added order in back 2025-05-27 14:44:33 +02:00
Thomas Fransolet
e3639589b0 Clean + reorder quiz + menu + working all 2025-05-23 15:32:30 +02:00
Thomas Fransolet
4713bc14dc Map -> Categories working ! 2025-05-14 16:56:14 +02:00
Thomas Fransolet
f4919801b7 Misc 2025-05-13 17:18:59 +02:00
Thomas Fransolet
cef69a61df section (to be tested when front) + section map controller + section quiz controller + update useless entities to DTO 2025-03-24 17:18:59 +01:00
Thomas Fransolet
a4bde1a8f4 wip section controller 2025-03-20 17:52:45 +01:00
Thomas Fransolet
9aa5e7df5e Resource controller done + section WIP 2025-03-20 16:25:23 +01:00
Thomas Fransolet
11cfeab210 Updated DB (wip) 2025-03-19 08:57:29 +01:00
Thomas Fransolet
8c18825c5d Wip clean code and pass by postgres ! 2025-03-06 17:39:08 +01:00
Thomas Fransolet
68b45333da Update weather section on get configuration detail 2024-04-10 17:15:10 +02:00
Thomas Fransolet
b5a3336670 Export fix 2024-03-21 16:02:31 +01:00
Thomas Fransolet
78f402a840 Fix hash issue 2024-02-14 14:57:09 +01:00
Thomas Fransolet
cc89f7b717 redirect to 1.2.0 + update entities and other for visitNamur 2023-12-15 17:00:07 +01:00
Thomas Fransolet
0b0b54c6af Add pinCode to TokenDTO 2023-12-05 14:08:00 +01:00
Thomas Fransolet
1e31ebe094 Add pinCode to instance + endpoints 2023-11-30 15:42:18 +01:00
74101d9939 Add sectionIds to ConfigurationDTO 2023-02-15 10:16:18 +01:00
3e6439433e Update get ressources endpoint by adding types filter 2022-10-27 13:31:38 +02:00
cf2d7759de Split resource in Resource and resourceData + Add Audio ResourceType 2022-10-26 17:11:45 +02:00
3b6c480cc6 Add instance model, dto, controller and service + update other objects and controllers 2022-10-06 17:07:11 +02:00
35e2d8296b Add password verification ! 2022-10-06 15:14:08 +02:00
Thomas Fransolet
16d4d6335d Delete config link in devices if used + mqtt client not retained message 2021-08-24 20:20:07 +02:00
Thomas Fransolet
0ee024e8ef Add identifier, dateupdate + MapType to MapTypeApp 2021-07-14 20:36:13 +02:00
Thomas Fransolet
43e3ce2922 missing file last commit 2021-07-08 19:14:15 +02:00
Thomas Fransolet
fed416d078 ressource to reSource + fixs + resourceController (upload and show) 2021-05-07 22:57:06 +02:00
Thomas Fransolet
65e7c7f890 Add device controller + DeviceDetailDTO + create section (all in Data as JSON) + delete all section from config 2021-05-04 16:52:39 +02:00
Thomas Fransolet
1f8fe1b415 Add device dto and model + add get section from configId 2021-04-30 20:02:26 +02:00
Thomas Fransolet
c5b025af1e Display to configuration + update dtos + appsettings mongodb connection string 2021-04-10 22:56:42 +02:00
Thomas Fransolet
1a1d7ccd9e First version of all structure 2021-04-06 18:17:23 +02:00
Thomas Fransolet
629e9c31f7 First commit - init service with swagger, user controller + auth controller 2021-04-06 16:17:54 +02:00