Skip to content

Correctifs veille RSS, notifications, agents IA et catégories#96

Merged
zinkh merged 4 commits into
mainfrom
claude/appels-offres-rss-surveillance-h7cj3h
Jul 22, 2026
Merged

Correctifs veille RSS, notifications, agents IA et catégories#96
zinkh merged 4 commits into
mainfrom
claude/appels-offres-rss-surveillance-h7cj3h

Conversation

@zinkh

@zinkh zinkh commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Suite de correctifs et une nouvelle fonctionnalité opt-in, en continuation de la veille RSS des appels d'offres (PR #95, déjà mergée) :

  • Encodage RSS + date manquante (server/tenderRssPoller.ts, src/pages/Tenders.tsx, src/pages/TenderDetail.tsx) — les flux RSS français souvent publiés en ISO-8859-1/Windows-1252 étaient mal décodés (caractères accentués corrompus). Le flux est maintenant récupéré en octets bruts puis décodé avec l'encodage réellement déclaré (en-tête HTTP ou prologue XML) via iconv-lite. Corrige aussi l'affichage "Invalid Date" quand la date limite de soumission est vide.

  • "Marquer tout comme lu" qui ne persiste pas + liens sur les activités (server.ts, src/pages/Notifications.tsx) — le suivi de lecture était écrit sur team_members (roster séparé sans ligne garantie par utilisateur), donc la mise à jour ne persistait pas toujours. Déplacé vers profiles (une ligne garantie par utilisateur authentifié). Le nom de l'élément créé/modifié dans le fil d'activité est maintenant un lien cliquable vers l'élément (ou sa liste).

  • Accès web optionnel pour les agents IA (packages/archioffice-agents/) — nouvel outil fetch_url gated par un toggle par agent (web_fetch_enabled, off par défaut, jamais hérité d'un template), avec avertissement de sécurité explicite dans l'UI de configuration. Protections anti-SSRF dans webFetch.ts : résolution DNS + rejet des adresses privées/loopback/lien-local à chaque saut de redirection, timeout, plafond de taille de réponse, et instruction explicite au modèle de traiter le contenu récupéré comme une donnée, pas des instructions.

  • Contrainte unique globale sur les catégories (supabase/migrate_fix_categories_unique_per_tenant.sql) — contact_categories et project_categories avaient une contrainte UNIQUE (name) globale (reliquat d'avant le multi-tenant), causant un 500 dès qu'un tenant utilisait un nom de catégorie déjà pris par un autre tenant. Corrigé en UNIQUE (tenant_id, name).

Toutes les migrations Supabase associées ont déjà été appliquées manuellement sur le projet de production pendant le développement.

Test plan

  • npm run lint (tsc --noEmit) passe sans erreur
  • Migrations vérifiées appliquées en production (colonnes/contraintes confirmées via requêtes SQL)
  • Tester en conditions réelles après déploiement : ajout d'une source RSS avec caractères accentués, "marquer tout comme lu" après rafraîchissement, activation du toggle fetch_url sur un agent, création d'une catégorie de contact avec un nom déjà utilisé par un autre tenant

https://claude.ai/code/session_013DT2bkCp3f7xKaNfsxC4Kb


Generated by Claude Code

claude added 4 commits July 22, 2026 04:55
Les flux RSS français (marchesonline.com, BOAMP...) sont souvent publiés
en ISO-8859-1/Windows-1252. Le sondeur les récupérait avec responseType
'text', ce qui forçait un décodage UTF-8 et corrompait les caractères
accentués (ex. "déchèterie" -> "d�ch�terie"). Le flux est maintenant
récupéré en octets bruts puis décodé avec l'encodage réellement déclaré
(en-tête HTTP ou prologue XML) via iconv-lite.

Corrige aussi l'affichage "Invalid Date" dans la liste et la fiche détail
des appels d'offres lorsque la date limite de soumission est vide (cas
d'une annonce RSS convertie en appel d'offre sans date structurée).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013DT2bkCp3f7xKaNfsxC4Kb
…les activités

"Marquer tout comme lu" écrivait notifications_last_seen sur team_members,
filtré par auth_user_id + tenant_id. team_members est un roster séparé qui
n'a pas forcément de ligne pour chaque utilisateur authentifié, donc l'UPDATE
ne touchait parfois aucune ligne : le prochain rafraîchissement du fil (30s)
recalculait "non lu" à partir d'une valeur jamais mise à jour. Ce suivi est
déplacé vers profiles, qui a garantiment une ligne par utilisateur (clé
primaire = auth.users.id).

Le nom de l'élément créé/modifié dans "Notifications & Flux d'activité" est
maintenant un lien vers l'élément quand sa page existe (projet, appel
d'offres, CCTP...), ou vers la liste correspondante sinon.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013DT2bkCp3f7xKaNfsxC4Kb
…vé par défaut

Nouvelle capacité distincte de action_scopes (qui ne couvre que les
ressources CRUD internes) : un toggle par agent, dans sa page de
configuration, autorise l'outil Gemini fetch_url à récupérer le contenu
texte d'une page web publique. Off par défaut, jamais hérité d'un template,
avec un avertissement de sécurité explicite dans l'UI.

La récupération (packages/archioffice-agents/src/server/webFetch.ts) résout
le nom d'hôte et rejette les adresses privées/loopback/lien-local (dont les
endpoints de métadonnées cloud), à chaque saut de redirection, avant toute
requête sortante — limite le risque SSRF d'un outil qui, contrairement aux
autres, fait un vrai appel réseau externe plutôt que de boucler sur l'API
interne de l'app. Le contenu récupéré est explicitement présenté au modèle
comme une donnée à analyser, jamais comme des instructions à suivre.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013DT2bkCp3f7xKaNfsxC4Kb
…jets)

contact_categories et project_categories portaient une contrainte UNIQUE
(name) globale, reliquat d'avant le multi-tenant : dès qu'un tenant créait
une catégorie dont le nom était déjà pris par un AUTRE tenant (ex.
"Fournisseur", "Client"), l'insertion échouait en base avec "duplicate key
value violates unique constraint" et l'API renvoyait 500 sans détail
(confirmé via les logs Postgres). La contrainte est maintenant scopée par
tenant : UNIQUE (tenant_id, name).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013DT2bkCp3f7xKaNfsxC4Kb
@zinkh
zinkh merged commit a5f7ae9 into main Jul 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants