Claudy is the in-house web application for Les 4 Sources, a foundation whose activities are run by a collective of families living in Yvoir, Belgium, at the Domaine d'Ahinvaux. Read more in the wiki.
It is built on Ruby on Rails 7 and PostgreSQL.
See .ruby-version and .tool-versions.
There are no tests for now, but we want to introduce a test suite with the upcoming Spaces refactoring (see #8). Feel free to start using any test system that you feel comfortable with.
We deploy on a Akamai/Linode 2GB using Hatchbox. We might set a staging environment up on the same VPS once we start working collectively on the code.
Beforehand, get the encryption key for the development environment and add it to config/credentials/development.key.
Then install Ruby 3.4.10 and NodeJS 18.8.0.
Get the default environment variables values and add them to .env, or - for now - duplicate .env.example to .env.
And everything should go like a couque.
git clone git@github.com:les4sources/claudy.git
cd claudy
gem install bundler:2.6.9
bundle config build.nio4r --with-cflags="-Wno-incompatible-pointer-types"
bundle install
yarn install
brew install vips
rails db:create && rails db:migrate
rails db:seed
bin/vite dev &
rails s
Then use the Rails console to add a first user.
> User.create email: "[set email here]", password: "[set password here]"
You are ready to go! Open localhost:3000 and have fun!
Use rails db:seed to add lodgings, rooms and spaces to the database.
Emails are delivered using Postmark. Please ask Michael (it@les4sources.be) for credentials.
Les gardes (rôle « Veilleur·euse ») sont publiées sous forme de flux iCalendar abonnable, pour qu'elles apparaissent dans l'agenda personnel de chacun·e sans avoir à ouvrir Claudy.
S'abonner depuis Google Agenda
- Dans Google Agenda : Autres agendas → + → Ajouter à partir de l'URL.
- Coller
https://app.les4sources.be/watchman.ics?token=LE_JETON. - Valider. Google re-synchronise le flux tout seul (comptez plusieurs heures entre deux rafraîchissements — c'est Google qui décide, pas nous).
Le flux est en lecture seule et collectif : un seul événement journée
entière par jour, titré Garde : Ana & Bruno quand plusieurs personnes sont de
garde le même jour. Seuls les titulaires y figurent (les backup non), et
tout l'historique est exporté. Chaque événement renvoie vers le calendrier
Claudy du mois concerné.
Le jeton
L'URL n'est protégée que par le jeton ?token=…, comparé en temps constant à la
variable d'environnement WATCHMAN_ICS_TOKEN. Toute requête sans jeton, avec un
mauvais jeton, ou lorsque la variable n'est pas configurée reçoit un 404 : le
flux n'est jamais ouvert par défaut. Le jeton vaut donc accès en lecture aux noms
et aux dates de garde — il se transmet à la main, pas en clair sur le web.
Générer un jeton : ruby -rsecurerandom -e 'puts SecureRandom.urlsafe_base64(32)'.
⚠️ Déploiement Hatchbox. Après avoir ajouté ou changéWATCHMAN_ICS_TOKEN, il faut un vrai redémarrage de l'app, pas un simple déploiement : le hot-reload (SIGUSR2) ne recharge pas l'environnement, et la route continuerait de renvoyer 404 alors que la variable semble bien configurée.
Le site public (repo les4sources/les4sources-website, Astro statique) lit Claudy au build, sans authentification, sur trois endpoints en lecture seule, cacheables cinq minutes avec ETag, sans aucune donnée personnelle. Le contrat de référence est docs/CLAUDY.md du repo du site.
| Endpoint | Contenu |
|---|---|
GET /api/public/v1/events?from=&to= |
événements publiés (défaut : depuis un an), avec catégorie, résumé, description publique, lieu, prix, lien d'inscription, image |
GET /api/public/v1/experiences |
activités publiées, prix, porteur (prénom seul), créneaux à venir sur six mois avec places restantes |
GET /api/public/v1/event_categories |
catégories avec slug, couleur hex et pôle de la charte |
Publier. Un événement ou une activité est un brouillon tant que published_at est vide : rien n'en sort. Le bouton « Publier sur le site » (fiche de l'événement ou de l'activité) pose published_at et le slug — proposé depuis le titre et le mois (pizza-party-septembre-2026), modifiable dans le formulaire jusqu'à la publication, figé tant que la fiche est en ligne. « Dépublier » retire la fiche du site et garde le slug. Sur une catégorie, le pôle de la charte (sept valeurs) donne la couleur et le pictogramme des cartes ; la couleur est un hex.
Dupliquer. « Dupliquer » sur un événement ouvre le formulaire de création prérempli (titre, résumé, description publique, catégorie, lieu, prix, lien, image) sans dates ni slug : rien n'est écrit avant « Enregistrer », et la copie reste un brouillon.
Reconstruction du site. Toute sauvegarde d'une fiche publiée (ou sa publication, dépublication, suppression) enfile WebsiteRebuildJob, qui regroupe les demandes sur deux minutes puis fait un POST sur WEBSITE_REBUILD_WEBHOOK_URL (webhook de déploiement Coolify ou repository_dispatch GitHub), avec Authorization: Bearer WEBSITE_REBUILD_WEBHOOK_TOKEN si le jeton est renseigné. Sans URL, le job journalise et ne fait rien ; hors production il ne fait rien non plus, sauf WEBSITE_REBUILD_ALLOW_NON_PRODUCTION=1.
Une Pizza Party privée réservée et payée sur Tranches de Vie (repo les4sources/tranchesdevie2) est souvent liée à un séjour de groupe géré ici. Quand le séjour est facturable, la party doit figurer sur sa facture. Depuis la fiche séjour, le bloc « Pizza Party » (sous les paiements) liste les parties privées payées autour des dates du séjour et permet d'en rattacher une.
Le rattachement est manuel et la relation est en lecture seule : Claudy interroge l'API de Tranches de Vie, ne lui écrit jamais. La party s'ajoute au Total séjour et le paiement miroir qu'elle crée (moyen tranchesdevie) compte dans l'Encaissé — un séjour soldé le reste. Ce paiement ne se modifie ni ne se supprime depuis la liste des paiements : on détache la party à la place. Une annulation ou un remboursement décidé là-bas est répercuté ici (la party sort du total, son paiement passe refunded), par le bouton « Actualiser » du bloc ou par le passage quotidien.
| Variable | Défaut | Rôle |
|---|---|---|
TRANCHESDEVIE_API_URL |
https://tranchesdevie.les4sources.be |
Base de l'API de Tranches de Vie. |
TRANCHESDEVIE_API_KEY |
— | Jeton Authorization: Bearer. Sans elle, aucun appel sortant : le bouton de rattachement est rendu désactivé avec l'explication. |
Contrat de l'API côté Tranches de Vie : les4sources/tranchesdevie2#290.
Claudy envoie plusieurs emails par des tâches rake idempotentes, lancées par le cron de Hatchbox. Toutes sont sans effet si on les rejoue : elles horodatent ce qu'elles ont envoyé.
| Tâche | Cadence | Ce qu'elle fait |
|---|---|---|
bundle exec rake activity_emails:send |
quotidienne | Invitation à choisir ses activités, ~30 jours avant l'arrivée. |
bundle exec rake activity_emails:balance_reminder |
quotidienne | Relance du solde exigible, ~14 jours avant l'arrivée. |
bundle exec rake coworking:send_expiry_reminders |
quotidienne | Rappel d'expiration des packs de coworking. |
bundle exec rake kitchen:weekly_digest |
vendredi 07:00 | Programme cuisine des 14 prochains jours, un email par responsable. |
bundle exec rake kitchen:bread_reminders |
quotidienne 07:00 | Rappel de commander le pain, 5 jours avant chaque prestation acceptée. |
bundle exec rake tranches_de_vie:sync_parties |
quotidienne | Répercute les annulations et remboursements des Pizza Party depuis Tranches de Vie. Sans effet si TRANCHESDEVIE_API_KEY est absente. |
Fuseau horaire des crons : Europe/Brussels.
Le digest se garde d'un double envoi par semaine ISO (clé Setting kitchen.digest_last_sent_on) : le relancer le samedi n'envoie rien. FORCE=1 bundle exec rake kitchen:weekly_digest passe outre, pour les essais.