Le callback Dash _update_table est un endpoint serveur global
(/_dash-update-component) invocable indépendamment du layout : la garde
is_admin() de layout() ne protégeait que l'affichage. Sans contrôle dans
le callback, un non-admin — voire un anonyme sur le chemin lecture —
pouvait lire/écrire toute la base SQLite utilisateurs.
Ajoute la garde en tête du callback + test de non-régression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_reabo_button() ne prend plus has_used_trial (bouton de réabonnement
simplifié) ; les deux anciens tests testant M'abonner/Me réabonner
sont remplacés par un test du comportement actuel.
Le callback _toggle_submit référence inf-cb-retractation/inf-cb-cgu en
Input sans condition, mais ces cases n'étaient rendues qu'en mode
"subscribe". Elles sont maintenant toujours montées (masquées et
pré-cochées en mode configure) pour que Dash trouve toujours ces ids.
Ajoute la fonction _change_hint et étend le callback _select_plan pour afficher
un hint "changement à la prochaine échéance" quand l'utilisateur sélectionne
un plan différent du plan courant.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remplace la garde ?plan= (redirection si absent) par une sélection
interactive : deux cartes de formule (réutilisées de la page publique via
_plan_card) cliquables, un callback recopie le choix dans un champ caché
natif soumis avec le POST du formulaire. Le bouton de soumission reste
désactivé tant qu'aucune formule n'est choisie (en plus des cases à
cocher existantes).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Remplace le CTA du bas de /connexion ("Créer un compte avec mon adresse
email" → "/inscription") par un lien vers l'offre d'abonnement ("Pas encore
de compte ? Voir les abonnements" → "/a-propos/abonnement").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Remplace le hack `str(int(prix_ht) * 1.2).replace('.0', '')` par
`round(prix_ht * 1.2, 2)` formaté avec `:g`, qui gère correctement les
prix qui ne multiplient pas proprement par 1.2 (ex: 24 -> 28.8 au lieu
de 28.799999999999997).
Le fichier src/pages/compte/abonnement.py contient le même bug mais
n'est pas touché ici : sa copie de _plan_card sera supprimée à la
tâche 4 du plan de refonte du tunnel d'abonnement.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
La page publique /a-propos/abonnement affiche désormais les cards de
formule (informatives, sans bouton par card), un explainer des
fonctionnalités, et un unique CTA "Je m'abonne" dont la cible dépend
de l'état d'authentification/abonnement. Retrait de la sous-section
"Fonctionnalités incluses" des CGU (doublon avec l'explainer).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Quand l'utilisateur valide son email, il est maintenant connecté automatiquement
via login_user() et redirigé directement vers /compte/abonnement/mes-infos
au lieu de /connexion?verified=1.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- IntegrityError (ex: email déjà utilisé) est désormais capturée et
reconvertie en ValueError, pour rester dans le funnel d'alerte
existant du callback admin au lieu de faire planter le callback Dash.
- Une UPDATE qui touche 0 ligne (ligne supprimée entre le chargement du
tableau et la soumission de l'édition) lève désormais une ValueError
au lieu d'être silencieusement traitée comme un succès (ce qui aurait
créé un log d'audit trompeur).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
La prop `bounds` de dash-leaflet lève une exception JS (TypeError sur
.equals() avec une valeur précédente indéfinie) au tout premier montage
du composant Map — confirmé via la stack trace navigateur, reproductible
même en dernière version (1.1.3). Cette exception laissait le clustering
dans un état incohérent (declustering impossible avant un zoom complet
suivi d'un dézoom).
bounds_to_center_zoom calcule un center/zoom approximatifs (projection
Web Mercator, taille de conteneur supposée) pour cadrer l'organisme et
ses contreparties, en évitant complètement la prop `bounds` bugguée.
Cadrage moins précis qu'un vrai fitBounds() navigateur, mais robuste.
Covers anonymous/non-admin access (404), and the full admin flow: switch
table, edit a subscriptions.prix_ht cell through the real DataTable UI,
verify the write lands in the DB, and verify the edit is logged and
visible in the admin_actions table.
tests/users.test.sqlite is committed and shared by the whole Selenium
session, so _cleanup_user deletes every row the tests create. Beyond
resetting sqlite_sequence (already needed for the AUTOINCREMENT
counter), a plain DELETE also leaves stale, never-zeroed bytes behind
in the admin_actions/subscriptions b-tree pages once they go back to
zero rows, which byte-diffs the file even though its logical content
is unchanged. VACUUM rebuilds the file from live data only, producing
a deterministic page layout (verified empirically across independent
runs with different random test data). Re-baselined the fixture to
that canonical vacuumed state so `git status` stays clean after
running the suite.
Marque is_home=True sur les marqueurs du home_type (acheteur ou titulaire
selon la page) dans get_org_location_map. Côté client, pointToLayer donne
à ce point un rayon légèrement plus grand (8 vs 5) et le ramène toujours
au premier plan (bringToFront), indépendamment de l'ordre des couches.
When switching tables, the table-switch branch writes fresh data for the
new table, which re-fires the same callback with data_previous still
holding the old table's rows. find_changed_cell only checked row count
before diffing, so if the two tables happened to have the same number of
rows it would zip mismatched-schema dicts and report a spurious changed
cell (usually the PK column), producing a confusing red alert right after
switching tables.
test_admin_non_admin_gets_404 asserted the same /admin 404 that
anonymous visitors also get, without first confirming the login
actually went through. A broken login (falls back to /connexion on
bad credentials, unverified email, etc.) would leave the session
anonymous and the test would keep passing for the wrong reason,
silently degrading into a duplicate of test_admin_anonymous_gets_404.
Now waits for the post-login redirect to /compte/abonnement (this
user has no subscription) before exercising the admin guard.
Covers anonymous → 404, non-admin → 404, and the full admin flow
(list, detail, status change, journal) through a real login and a
real running app. tests/users.test.sqlite is committed and shared by
the whole Selenium session, so the test cleanup also resets the
sqlite_sequence high-water marks that plain DELETEs don't roll back,
keeping the file byte-stable across runs. This run additionally bakes
in migration 0006_create_admin_actions (new admin_actions table), the
first time any Selenium test has booted the real app since that
migration was added — the same one-time process by which migrations
0001-0005 already ended up committed in this fixture.
Wires is_admin(), SUBSCRIPTION_STATUSES/get_current/set_status, and
log_action() into POST /admin/actions/subscription-status: validates
the requested status and that subscription_id matches the user's
current subscription, applies the change, and logs an audit entry.
Registers the admin blueprint in init_auth() and documents ADMIN_EMAIL
in .template.env.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implement access control function for admin panel. Returns True only if
ADMIN_EMAIL env var is set, user is authenticated, and email matches
case-insensitively.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds four new DB helpers for the admin panel:
- SUBSCRIPTION_STATUSES: tuple of valid subscription statuses
- list_by_user(): retrieve all subscriptions for a user (newest first)
- set_status(): override a subscription's status and updated_at timestamp
- get_subscriber_state(): public wrapper for internal _get_state()
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements list_users(limit: int = 1000) -> list[sqlite3.Row] in the auth
DB layer to retrieve all users ordered by creation date (most recent first).
- Returns all users, optionally capped at limit (default 1000)
- Orders by created_at DESC to show newest users first
- Follows existing DB layer patterns using get_conn().execute().fetchall()
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
hosted_page_links.payment_info (POST /v1/subscription) n'honore pas
accept_url/cancel_url malgré la doc, même passés en query string (constaté
en test sur test.colibre.fr). On génère maintenant la page de paiement via
POST /v1/session/subscription (Checkout API), qui accepte ces champs dans
son body et redirige effectivement le navigateur — même mécanisme déjà
fonctionnel pour l'ajout de moyen de paiement (create_recurring_session).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>