Skip to content

[FEATURE] Add followed activities with email notifications#877

Open
florentdestremau wants to merge 3 commits intoClub-Alpin-Annecy:masterfrom
florentdestremau:feature/followed-activities-notifications
Open

[FEATURE] Add followed activities with email notifications#877
florentdestremau wants to merge 3 commits intoClub-Alpin-Annecy:masterfrom
florentdestremau:feature/followed-activities-notifications

Conversation

@florentdestremau
Copy link
Copy Markdown
Contributor

Résumé

Cette PR ajoute une fonctionnalité permettant aux utilisateurs de "suivre" des activités et de recevoir des notifications par email lorsqu'une nouvelle collective est publiée pour une activité suivie.

Fonctionnalités principales

🔔 Système de suivi d'activités

  • Auto-subscription intelligente : Lorsqu'un utilisateur s'inscrit à une collective, il suit automatiquement les activités de cette collective
  • Respect du désabonnement : Si un utilisateur s'est explicitement désabonné d'une activité, il ne sera pas réabonné automatiquement
  • Interface de gestion : Nouvelle page /profile/followed-activities pour voir et gérer les activités suivies

📧 Notifications par email

  • Bouton "Notifier les abonnés" : Visible sur les événements publiés pour les leaders/administrateurs
  • Une seule notification par utilisateur : Même si un utilisateur suit plusieurs activités de l'événement, il ne reçoit qu'un seul email
  • Lien de désabonnement sécurisé : Token signé avec itsdangerous, valide 30 jours, dans chaque email

🔄 Migration rétroactive

  • Peuplement automatique de la table user_followed_activities à partir des inscriptions existantes
  • Tous les utilisateurs ayant participé à des collectives sont automatiquement abonnés aux activités correspondantes

Architecture technique

Modèle de données

  • Nouvelle table user_followed_activities (clé composite : user_id + activity_type_id)
  • Champs : followed_at, explicitly_unfollowed, unfollowed_at
  • Relations bidirectionnelles entre User et ActivityType

Routes ajoutées

  • GET /profile/followed-activities - Liste des activités suivies
  • POST /profile/follow-activity/<id> - Suivre une activité
  • POST /profile/unfollow-activity/<id> - Ne plus suivre
  • GET /profile/unfollow-activity/<id>/token/<token> - Désabonnement via email
  • POST /event/<id>/notify-followers - Notifier les abonnés (leaders uniquement)

Fichiers modifiés

Nouveaux fichiers

  • collectives/models/user_followed_activity.py - Modèle d'association
  • collectives/templates/profile/followed_activities.html - Interface utilisateur
  • migrations/versions/c84f9ec33732_add_user_followed_activities.py - Migration Alembic
  • tests/unit/test_followed_activities.py - Tests unitaires (5 tests)

Fichiers modifiés

  • collectives/models/user/model.py - Relations et méthodes helper
  • collectives/models/activity_type.py - Relation inverse
  • collectives/models/__init__.py - Export du nouveau modèle
  • collectives/routes/event.py - Auto-subscription + route notification
  • collectives/routes/profile.py - Routes de gestion
  • collectives/email_templates.py - Fonction d'envoi d'emails
  • collectives/configuration.yaml - Templates de messages email
  • collectives/templates/event/partials/admin.html - Bouton notification
  • collectives/templates/partials/main-navigation.html - Lien menu profil

Points à reviewer

1. Sécurité

  • Vérifier la validation du token de désabonnement (30 jours max)
  • Confirmer que seuls les leaders peuvent envoyer des notifications
  • Vérifier qu'on ne notifie que pour les événements au statut "Confirmé"

2. Performance

  • La requête de récupération des abonnés utilise activity.followers_assoc - acceptable pour des événements avec < 1000 abonnés ?
  • L'envoi d'emails est synchrone - à monitorer si beaucoup d'abonnés

3. Migration

  • La migration peuple la table depuis les inscriptions existantes - vérifier la requête SQL sur un dump de prod
  • Revue de la logique explicitly_unfollowed pour éviter les réabonnements forcés

4. UX

  • Le wording "Mes activités suivies" dans le menu est-il clair ?
  • Le template d'email est-il bien formaté et informatif ?

5. Codestyle

  • Dans collectives/routes/event.py je dois ajouter la logique à 3 endroits différents donc dommage, mais pour le coup je ne suis pas au clair sur comment on pourrait factoriser ça.
  • Je ne vois pas comment tester les mails en local, il faut installer soi-même un mailcatcher ou equivalent ?

Tests

# Tests unitaires
uv run pytest tests/unit/test_followed_activities.py -v

# Tous les tests
uv run pytest tests/unit/ -v

Résultat : 37 tests passent (dont 5 nouveaux pour cette feature)

Configuration requise

Les nouvelles clés de configuration sont automatiquement créées au démarrage :

  • EVENT_NOTIFICATION_TO_FOLLOWERS_SUBJECT - Sujet de l'email
  • EVENT_NOTIFICATION_TO_FOLLOWERS_MESSAGE - Corps du template

Checklist

  • Code formaté avec ruff
  • Tests unitaires passent
  • Migration Alembic testée (up/down)
  • Serveur démarre correctement
  • Fonctionnalité testée manuellement (inscription, notification, désabonnement)
  • Documentation (plan) créée dans plans/

Captures d'écran

image image image image image
  1. Page "Mes activités suivies" dans le profil
  2. Bouton "Notifier les abonnés" sur un événement
  3. Exemple d'email reçu

Note pour les reviewers : Cette feature a été développée en suivant le plan détaillé dans plans/followed-activities-notifications.md.

This feature allows users to follow activities and receive email notifications
when new events are published for those activities.

Key features:
- Auto-subscribe to activities when registering for events (unless explicitly unsubscribed)
- User profile page to manage followed activities (/profile/followed-activities)
- Notify followers button on published events for leaders
- Secure unsubscribe links in notification emails (30-day signed tokens)
- Retroactive migration: auto-populates from existing registrations

New files:
- collectives/models/user_followed_activity.py: Association model
- collectives/templates/profile/followed_activities.html: Management UI
- migrations/versions/c84f9ec33732_add_user_followed_activities.py: DB migration
- tests/unit/test_followed_activities.py: Unit tests
- plans/followed-activities-notifications.md: Implementation plan

Modified:
- User/ActivityType models with relationships and helper methods
- Event routes for auto-subscription and notification endpoint
- Profile routes for activity management
- Email templates and configuration
- Navigation and event admin templates
Copy link
Copy Markdown
Contributor

@gdaviet gdaviet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merci pour la PR @florentdestremau !
Effectivement ce serait bien si @acapai peux jeter un oeil pour voir comment les implémentations peuvent se combiner

Mon avis personnel c'est la registration automatique n'est pas forcément désirable, le laisser opt-in en proposant de suivre les activités dans son profile me semble suffisant.

Sinon quelques remarques de Claude qui me semblent pertinentes :

  1. Security — token unsubscribe requires login (blocker)

unfollow_activity_with_token is a GET route but it calls current_user.id. This route is reached via a link in an email. If the recipient is
not logged in, current_user will be anonymous and the check token_user_id != current_user.id will always fail, making the unsubscribe link
completely broken. This is the most critical issue — the route must either work without authentication, or redirect to login first and then
complete the action.

  1. Performance — N+1 on follower collection (routes/event.py:577-580)

for activity in event.activity_types:
for assoc in activity.followers_assoc:
followers_assoc is lazy=True, so this fires one query per activity. On a large club with many activities and followers, this could be slow.
Should use an explicit join query or eager-load followers_assoc before the loop.

Pour le AGENTS.md : c'est une bonne chose de l'ajouter, par contre peut être dans une PR séparée ?

@acapai
Copy link
Copy Markdown

acapai commented Mar 30, 2026

Salut,

Effectivement quand j'avais écris cette issue #873 , j'avais déjà entamé un travail sur une branche . Je viens de faire le tour de ton travail , et j'ai rassemblé les retours fait par @jnguiot ici : #873 (comment) .

La PR que je propose rassemble un peu tout ça !

Si vous voulez tester/faire des route sur la PR #886 que j'ai ouverte n'hésitez pas .

Bonne lecture !

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.

3 participants