Compare commits
7 Commits
m14-deploy
...
docs/guide
| Author | SHA1 | Date | |
|---|---|---|---|
|
bb8a6539bc
|
|||
|
4ac5be4c8d
|
|||
|
fdd3310462
|
|||
|
0be02a660a
|
|||
|
d85733116a
|
|||
|
2deeb83c76
|
|||
|
b518508632
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -52,7 +52,9 @@ Thumbs.db
|
|||||||
|
|
||||||
# --- Local scratch / WIP files ---
|
# --- Local scratch / WIP files ---
|
||||||
FIXME_*
|
FIXME_*
|
||||||
|
FEAT_*
|
||||||
TEST_*
|
TEST_*
|
||||||
|
HANDOFF.md
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
|
||||||
# --- Logs ---
|
# --- Logs ---
|
||||||
|
|||||||
28
CHANGELOG.md
Normal file
28
CHANGELOG.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-09-08
|
||||||
|
|
||||||
|
Initial release covering milestones M1 through M15.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **M1 (Scaffolding)**: Python project structure with `pyproject.toml`, and tooling configuration for `ruff`, `mypy`, `bandit`, and `pre-commit`.
|
||||||
|
- **M2 (Configuration & secrets)**: Pydantic Settings for configuration management, `SecretStr` for sensitive fields, and redaction utilities (`redact_url`, `redact_secrets`, `redact_exception`) with `RedactingFormatter` for logging.
|
||||||
|
- **M3 (Data models)**: 16 Pydantic models and 6 enums across 10 modules, including frozen contracts and mutable work results.
|
||||||
|
- **M4 (Pronote sources)**: iCal fetch and parse, `pronotepy.ParentClient` integration, automatic fallback logic for `auto`, `ical`, and `pronotepy` modes, and error redaction for sensitive data.
|
||||||
|
- **M5 (Blog RSS)**: `feedparser`-based RSS client with GUID deduplication, HTTP cache support (ETag/If-Modified-Since), and `BlogRSSState` persistence.
|
||||||
|
- **M6 (Theoretical agenda)**: JSON provider with week parity (even/odd), school holidays calendar, and deterministic IDs for events.
|
||||||
|
- **M7 (CalDAV sync)**: Differential synchronization by UID, `X-PRONOTE-SYNC-MANAGED` marker for managed events, idempotent operations, preserved cancelled events, and dry-run support.
|
||||||
|
- **M8 (Agenda diff)**: `AgendaComparator` with deterministic matching, and generation of `AgendaDiff`/`AgendaChange` objects for tracking differences.
|
||||||
|
- **M9 (AI synthesis)**: `SynthesisProvider` protocol, OpenAI provider, optional `litellm` provider, and `openai-compatible` provider with degraded mode (returns `None` on failure).
|
||||||
|
- **M10 (XMPP channel)**: `XmppChannel` using `slixmpp`, formatted messages (synthesis, homeworks, changes, messages, blog), and error handling that returns `False` on failure.
|
||||||
|
- **M11 (Pipeline orchestration)**: `PipelineRunner` as composition root, 7 pipeline steps, degraded error handling, dry-run mode, and iCal reuse within a single run.
|
||||||
|
- **M12 (CLI entry point)**: `pronote-sync` command with `--dry-run` and `--log-level` options, redacted error display, and safe traceback in DEBUG mode.
|
||||||
|
- **M13 (Tests & coverage)**: 636 tests with 95.67% coverage, test fixtures (`pronote-4e.ics`, `pronote-6e.ics`), shared `conftest.py`, and secret non-leak tests.
|
||||||
|
- **M14 (Deployment)**: systemd service and timer (daily at 18:00), logrotate configuration (daily, rotate 7, compress), `check_secrets.py` pre-deployment scanner, and exploitation guide.
|
||||||
|
- **M15 (Documentation)**: README, README.LLM.md (AI agent setup guide), MIT LICENSE, CHANGELOG, and Gitea Actions CI/CD reference for LXC/VPS (Debian/CentOS).
|
||||||
|
- **Other**: MIT License. Gitea Actions CI/CD reference for LXC/VPS (Debian/CentOS) is planned and optional, not delivered in this release.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Guide de Développement : Synchronisation Pronote → CalDAV + XMPP (Python)
|
# Guide de Développement : Synchronisation Pronote → CalDAV + XMPP (Python)
|
||||||
|
|
||||||
> **Statut** : Guide de référence pour un futur projet Python inspiré de [`pronote-digest`](https://github.com/antoine-coulon/pronote-digest) (TypeScript).
|
> **Statut** : Guide de référence pour un futur projet Python inspiré de [`pronote-digest`](https://github.com/yoanbernabeu/pronote-digest) (TypeScript) par [Yoan Bernabeu](https://yoanbernabeu.github.io/pronote-digest/).
|
||||||
> **Public cible** : Développeurs Python (≥ 3.13.5) familiers avec les concepts de CLI, synchronisation de calendriers et messagerie instantanée.
|
> **Public cible** : Développeurs Python (≥ 3.13.5) familiers avec les concepts de CLI, synchronisation de calendriers et messagerie instantanée.
|
||||||
> **Objectif** : Fournir une base architecturale et technique pour un outil **synchronisant l'agenda Pronote vers CalDAV**, **comparant avec un agenda théorique**, **récupérant messages et informations**, et **envoyant une synthèse par XMPP**.
|
> **Objectif** : Fournir une base architecturale et technique pour un outil **synchronisant l'agenda Pronote vers CalDAV**, **comparant avec un agenda théorique**, **récupérant messages et informations**, et **envoyant une synthèse par XMPP**.
|
||||||
|
|
||||||
@@ -6090,7 +6090,7 @@ Ce guide fournit une **base architecturale et technique solide** pour développe
|
|||||||
1. **Créer le dépôt** : Initialiser un nouveau dépôt Python avec la structure proposée.
|
1. **Créer le dépôt** : Initialiser un nouveau dépôt Python avec la structure proposée.
|
||||||
2. **Implémenter le cœur** : Commencer par les modules `models/`, `sources/pronote/ical.py` et `utils/`.
|
2. **Implémenter le cœur** : Commencer par les modules `models/`, `sources/pronote/ical.py` et `utils/`.
|
||||||
3. **Ajouter les tests** : Écrire des tests unitaires pour chaque module dès le début.
|
3. **Ajouter les tests** : Écrire des tests unitaires pour chaque module dès le début.
|
||||||
4. **Configurer CI/CD** : Mettre en place GitHub Actions pour exécuter les tests et vérifier la sécurité.
|
4. **Configurer Gitea Actions** : Mettre en place Gitea Actions pour exécuter les tests et vérifier la sécurité, en vue d'un déploiement sur LXC/VPS (Debian/CentOS).
|
||||||
5. **Tester en conditions réelles** : Utiliser des flux iCal Pronote anonymisés pour valider le parsing.
|
5. **Tester en conditions réelles** : Utiliser des flux iCal Pronote anonymisés pour valider le parsing.
|
||||||
|
|
||||||
> **⚠️ Rappel** : Ce guide est **volontairement détaillé** pour préserver les connaissances acquises sur les spécificités de Pronote. Certaines sections (ex: parsing iCal) contiennent des **observations précises** issues de l'analyse du code TypeScript existant. **Ne pas sous-estimer l'importance de ces détails** : ils sont critiques pour un fonctionnement fiable du projet.
|
> **⚠️ Rappel** : Ce guide est **volontairement détaillé** pour préserver les connaissances acquises sur les spécificités de Pronote. Certaines sections (ex: parsing iCal) contiennent des **observations précises** issues de l'analyse du code TypeScript existant. **Ne pas sous-estimer l'importance de ces détails** : ils sont critiques pour un fonctionnement fiable du projet.
|
||||||
|
|||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Antoine Van Elstraete
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
147
README.LLM.md
Normal file
147
README.LLM.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# pronote-sync — AI Agent Setup Guide
|
||||||
|
|
||||||
|
This document guides an AI agent through installing and pre-configuring the `pronote-sync` project on a fresh Linux host (Debian/CentOS). It covers environment setup, dependency installation, and configuration file preparation. It does **NOT** cover secrets provisioning — those must be provided by the operator.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Python ≥ 3.13.5 (check with `python3 --version`)
|
||||||
|
- Git
|
||||||
|
- A non-root service user (e.g., `pronote-sync`)
|
||||||
|
- Target paths:
|
||||||
|
- `/opt/pronote-sync` (code)
|
||||||
|
- `/var/lib/pronote-sync` (state)
|
||||||
|
- `/var/log/pronote-sync` (logs)
|
||||||
|
- `/etc/pronote-sync` (config)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation Steps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create service user
|
||||||
|
sudo useradd --system --no-create-home --shell /usr/sbin/nologin pronote-sync
|
||||||
|
|
||||||
|
# Clone the repository
|
||||||
|
sudo git clone <repo-url> /opt/pronote-sync
|
||||||
|
sudo chown -R pronote-sync:pronote-sync /opt/pronote-sync
|
||||||
|
|
||||||
|
# Create virtual environment
|
||||||
|
cd /opt/pronote-sync
|
||||||
|
sudo -u pronote-sync python3.13 -m venv .venv
|
||||||
|
sudo -u pronote-sync .venv/bin/pip install -e ".[dev]"
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
sudo install -d -m 0700 -o pronote-sync -g pronote-sync /etc/pronote-sync
|
||||||
|
sudo install -d -m 0750 -o pronote-sync -g pronote-sync /var/lib/pronote-sync
|
||||||
|
sudo install -d -m 0750 -o pronote-sync -g pronote-sync /var/log/pronote-sync
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Preparation (Without Secrets)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy the example config
|
||||||
|
sudo -u pronote-sync cp /opt/pronote-sync/.env.example /etc/pronote-sync/pronote-sync.env
|
||||||
|
|
||||||
|
# The operator must fill in secrets (PRONOTE_PASSWORD, CALDAV_PASSWORD, XMPP_PASSWORD, AI_API_KEY, etc.)
|
||||||
|
# Do NOT populate secrets automatically — leave them for the operator.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Non-Secret Environment Variables (Pre-Configurable)
|
||||||
|
|
||||||
|
The following variables can be safely pre-configured in `/etc/pronote-sync/pronote-sync.env`:
|
||||||
|
|
||||||
|
- **Pronote:**
|
||||||
|
- `PRONOTE_ACCOUNT_TYPE` (default: `parent`)
|
||||||
|
- `PRONOTE_ENT` (ENT slug, e.g., `lyceeconnecte`)
|
||||||
|
- `PRONOTE_AGENDA_SOURCE`, `PRONOTE_HOMEWORK_SOURCE`, `PRONOTE_MESSAGES_SOURCE` (`auto`, `ical`, or `pronotepy`)
|
||||||
|
|
||||||
|
- **CalDAV:**
|
||||||
|
- `CALDAV_CALENDAR_PATH` (e.g., `/pronote-sync/`)
|
||||||
|
- `CALDAV_ALLOW_INSECURE_HTTP` (default: `false`)
|
||||||
|
|
||||||
|
- **Sync Window:**
|
||||||
|
- `SYNC_PAST_DAYS`, `SYNC_FUTURE_DAYS`
|
||||||
|
|
||||||
|
- **Theoretical Agenda:**
|
||||||
|
- `THEORETICAL_AGENDA_PATH`, `SCHOOL_HOLIDAYS_PATH`
|
||||||
|
- `THEORETICAL_WEEK_ANCHOR_DATE`, `THEORETICAL_WEEK_ANCHOR_TYPE`
|
||||||
|
|
||||||
|
- **XMPP:**
|
||||||
|
- `XMPP_ENABLED`, `XMPP_HOST`, `XMPP_PORT`, `XMPP_USE_TLS`, `XMPP_TIMEOUT`, `XMPP_RESOURCE`
|
||||||
|
|
||||||
|
- **AI:**
|
||||||
|
- `AI_ENABLED`, `AI_PROVIDER`, `AI_BASE_URL`, `AI_MODEL`, `AI_ALLOW_INSECURE_HTTP`
|
||||||
|
|
||||||
|
- **Blog:**
|
||||||
|
- `BLOG_ENABLED`, `BLOG_RSS_URL`
|
||||||
|
|
||||||
|
- **General:**
|
||||||
|
- `DRY_RUN`, `LOG_LEVEL`
|
||||||
|
|
||||||
|
### Secret Variables (Operator Must Provide)
|
||||||
|
|
||||||
|
**Do NOT set these variables automatically.** The operator must manually provide the following secrets:
|
||||||
|
|
||||||
|
- **Pronote:**
|
||||||
|
- `PRONOTE_ICAL_URL`, `PRONOTE_URL`, `PRONOTE_USERNAME`, `PRONOTE_PASSWORD`
|
||||||
|
|
||||||
|
- **CalDAV:**
|
||||||
|
- `CALDAV_URL`, `CALDAV_USERNAME`, `CALDAV_PASSWORD`
|
||||||
|
|
||||||
|
- **XMPP:**
|
||||||
|
- `XMPP_JID`, `XMPP_PASSWORD`, `XMPP_TO`
|
||||||
|
|
||||||
|
- **AI:**
|
||||||
|
- `AI_API_KEY`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Deployment Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify no secrets in the codebase
|
||||||
|
/opt/pronote-sync/.venv/bin/python /opt/pronote-sync/scripts/check_secrets.py
|
||||||
|
|
||||||
|
# Verify dependencies
|
||||||
|
/opt/pronote-sync/.venv/bin/python -m pip check
|
||||||
|
|
||||||
|
# Dry-run test (will fail without secrets, but verifies the environment)
|
||||||
|
/opt/pronote-sync/.venv/bin/pronote-sync --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## systemd Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install systemd units
|
||||||
|
sudo install -m 0644 /opt/pronote-sync/deploy/systemd/pronote-sync.service /etc/systemd/system/
|
||||||
|
sudo install -m 0644 /opt/pronote-sync/deploy/systemd/pronote-sync.timer /etc/systemd/system/
|
||||||
|
|
||||||
|
# Install logrotate config
|
||||||
|
sudo install -m 0644 /opt/pronote-sync/deploy/logrotate/pronote_sync /etc/logrotate.d/pronote_sync
|
||||||
|
|
||||||
|
# Reload and enable
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now pronote-sync.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes for the AI Agent
|
||||||
|
|
||||||
|
- **Do NOT commit or write secrets** to any file in the repository.
|
||||||
|
- **Do NOT modify** `.gitignore`, `pyproject.toml`, or existing source files.
|
||||||
|
- If `python3.13` is not available, install it first:
|
||||||
|
- Debian: `sudo apt install python3.13`
|
||||||
|
- CentOS: Compile from source or use `dnf` if available.
|
||||||
|
- The `check_secrets.py` script exits with:
|
||||||
|
- `0` (clean)
|
||||||
|
- `1` (secrets found)
|
||||||
|
- `2` (error)
|
||||||
|
- All paths in the systemd unit assume `/opt/pronote-sync` — adjust if installed elsewhere.
|
||||||
|
- The operator **must** provide real values for all **SECRET** variables before enabling the timer.
|
||||||
69
README.md
Normal file
69
README.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# pronote-sync
|
||||||
|
|
||||||
|
Synchronisation Pronote → CalDAV + XMPP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Synchronise l'agenda et les devoirs de **Pronote** vers un calendrier **CalDAV** et envoie un résumé quotidien par **XMPP**. Supporte les sources iCal et `pronotepy` avec repli automatique. Synthèse IA optionnelle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Démarrage rapide
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Cloner le dépôt
|
||||||
|
git clone <repo-url>
|
||||||
|
cd pronote-sync
|
||||||
|
|
||||||
|
# Créer l'environnement virtuel
|
||||||
|
python3.13 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
# Installer
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
|
||||||
|
# Configurer
|
||||||
|
cp .env.example .env
|
||||||
|
# Éditer .env avec vos paramètres (voir .env.example pour le détail)
|
||||||
|
|
||||||
|
# Tester
|
||||||
|
pronote-sync --dry-run --log-level DEBUG
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Utilisation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pronote-sync # Exécute la synchronisation
|
||||||
|
pronote-sync --dry-run # Simulation sans écriture
|
||||||
|
pronote-sync --log-level DEBUG # Verbosité des journaux
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Déploiement
|
||||||
|
|
||||||
|
Les artefacts pour **systemd/timer** et **logrotate** sont fournis dans `deploy/`. Voir [docs/exploitation.md](docs/exploitation.md) pour plus de détails.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🙏 Remerciements
|
||||||
|
|
||||||
|
Ce projet repose sur les bibliothèques open-source suivantes :
|
||||||
|
- [pronotepy](https://github.com/bain3/pronotepy) — client Pronote
|
||||||
|
- [icalendar](https://github.com/collective/icalendar) — parsing iCal
|
||||||
|
- [caldav](https://github.com/python-caldav/caldav) — client CalDAV
|
||||||
|
- [slixmpp](https://github.com/poezio/slixmpp) — client XMPP
|
||||||
|
- [pydantic](https://github.com/pydantic/pydantic) — validation et configuration
|
||||||
|
- [openai](https://github.com/openai/openai-python) — synthèse IA
|
||||||
|
- [feedparser](https://github.com/kurtmckee/feedparser) — parsing RSS
|
||||||
|
- [beautifulsoup4](https://www.crummy.com/software/BeautifulSoup/) — parsing HTML
|
||||||
|
|
||||||
|
Inspiré de [pronote-digest](https://github.com/yoanbernabeu/pronote-digest) par [Yoan Bernabeu](https://yoanbernabeu.github.io/pronote-digest/).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
MIT — voir [LICENSE](LICENSE).
|
||||||
12
TODO.md
12
TODO.md
@@ -294,13 +294,13 @@ Mettre en production de façon supervisée (planification, rotation des logs, v
|
|||||||
|
|
||||||
Rédiger la documentation utilisateur et finaliser le projet.
|
Rédiger la documentation utilisateur et finaliser le projet.
|
||||||
|
|
||||||
- [ ] Créer `README.md` (installation, configuration `.env`, usage CLI, systemd/docker, limites, RGPD).
|
- [x] Créer `README.md` (installation, configuration `.env`, usage CLI, systemd/docker, limites, RGPD).
|
||||||
- [ ] Documenter l'architecture (pipeline, modules) en résumé.
|
- [x] Documenter l'architecture (pipeline, modules) en résumé.
|
||||||
- [ ] Ajouter `CHANGELOG` initial et la licence (MIT).
|
- [x] Ajouter `CHANGELOG` initial et la licence (MIT).
|
||||||
- [ ] Revue finale : cohérence avec le guide, aucun secret documenté en clair.
|
- [x] Revue finale : cohérence avec le guide, aucun secret documenté en clair.
|
||||||
- [ ] (Optionnel) Configurer GitHub Actions CI/CD (pytest + bandit + ruff + mypy) d'après §Prochaines étapes.
|
- [ ] (Optionnel) Configurer Gitea Actions (pytest + bandit + ruff + mypy) pour le déploiement LXC/VPS (Debian/CentOS).
|
||||||
|
|
||||||
### Critères d'acceptation
|
### Critères d'acceptation
|
||||||
- `README.md` permet d'installer et de lancer le projet sans le guide.
|
- `README.md` permet d'installer et de lancer le projet sans le guide.
|
||||||
- La CI exécute tests + lint + sécurité.
|
- Gitea Actions exécute tests + lint + sécurité.
|
||||||
- Aucun secret dans la documentation.
|
- Aucun secret dans la documentation.
|
||||||
|
|||||||
31
data/school_holidays.json
Normal file
31
data/school_holidays.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"zone": "A",
|
||||||
|
"school_year": "2026-2027",
|
||||||
|
"periods": [
|
||||||
|
{
|
||||||
|
"start_date": "2026-10-17",
|
||||||
|
"end_date": "2026-11-02",
|
||||||
|
"label": "Toussaint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_date": "2026-12-19",
|
||||||
|
"end_date": "2027-01-04",
|
||||||
|
"label": "Noël"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_date": "2027-02-13",
|
||||||
|
"end_date": "2027-03-01",
|
||||||
|
"label": "Hiver"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_date": "2027-04-10",
|
||||||
|
"end_date": "2027-04-26",
|
||||||
|
"label": "Printemps"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_date": "2027-07-03",
|
||||||
|
"end_date": "2027-09-01",
|
||||||
|
"label": "Été"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user