diff --git a/.claude/settings.json b/.claude/settings.json index 843fc63..f424770 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -65,7 +65,17 @@ "Bash(find:*)", "Bash(.venv/bin/python:*)", "Bash(.venv/bin/pip install:*)", - "Bash(grep:*)" + "Bash(grep:*)", + "Bash(wc:*)", + "Bash(DB=/home/gilles/Documents/vscode/jardin/data/jardin.db)", + "Bash(__NEW_LINE_c71d992d355b0a42__ echo \"=== Comptages ===\")", + "Bash(__NEW_LINE_c71d992d355b0a42__ echo \"\")", + "Bash(__NEW_LINE_bc37df477a517ffd__ echo \"\")", + "Bash(__NEW_LINE_cef0a7fc7759860e__ echo \"\")", + "Bash(docker compose restart:*)", + "Bash(docker compose build:*)", + "Bash(__NEW_LINE_5f780afd9b58590d__ echo \"\")", + "Read(//home/gilles/.claude/projects/-home-gilles-Documents-vscode-jardin/**)" ], "additionalDirectories": [ "/home/gilles/Documents/vscode/jardin/frontend/src", diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..5091de5 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,90 @@ +# 🌿 Jardin — Application de gestion de jardins + +Interface web **mobile-first** pour gĂ©rer jardins, cultures, tĂąches et calendrier lunaire, avec dĂ©tection d'espĂšces via IA. +ThĂšme visuel : **Gruvbox Dark Seventies**. + +## đŸ—ïž Architecture du Projet + +Le projet est composĂ© de trois services principaux orchestrĂ©s par Docker Compose : + +1. **Backend (FastAPI)** : API REST gĂ©rant la logique mĂ©tier, la base de donnĂ©es (SQLite/SQLModel) et l'intĂ©gration des services (lunaire, mĂ©tĂ©o). +2. **Frontend (Vue 3)** : Interface utilisateur rĂ©active avec Vite, Pinia pour le store, et Tailwind CSS pour le style. +3. **AI Service (FastAPI + YOLO)** : Service spĂ©cialisĂ© dans la dĂ©tection et classification de plantes via un modĂšle YOLOv8 (`ultralytics`). +4. **Redis** : UtilisĂ© pour le cache et les tĂąches planifiĂ©es. + +## 🚀 DĂ©marrage Rapide + +### Avec Docker (RecommandĂ©) +```bash +cp .env.example .env +docker compose up --build +``` +- **Application** : [http://localhost:8061](http://localhost:8061) +- **API Documentation (Swagger)** : [http://localhost:8060/docs](http://localhost:8060/docs) + +### DĂ©veloppement Local + +#### Backend +```bash +cd backend +python -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt +# Variables d'env par dĂ©faut pour le dev local +export DATABASE_URL=sqlite:///./data/jardin.db +export UPLOAD_DIR=./data/uploads +uvicorn app.main:app --reload --port 8060 +``` + +#### Frontend +```bash +cd frontend +npm install +npm run dev -- --port 8061 +``` + +#### AI Service +```bash +cd ai-service +pip install -r requirements.txt +uvicorn main:app --reload --port 8070 +``` + +## đŸ› ïž Commandes de Test et QualitĂ© + +### Backend +```bash +cd backend +pytest tests/ -v +``` + +### Frontend +```bash +cd frontend +npm run lint # VĂ©rification TypeScript (vue-tsc) +``` + +## 📋 Conventions de DĂ©veloppement + +- **Langue** : Code en anglais (variables, fonctions, routes), commentaires et documentation en français. +- **Style Backend** : PEP 8. Utilisation de `SQLModel` pour les modĂšles de donnĂ©es (union de SQLAlchemy et Pydantic). +- **Style Frontend** : Composition API (Vue 3). Utilisation de TypeScript obligatoire. Tailwind CSS pour le styling atomique. +- **API** : PrĂ©fixe `/api` pour tous les endpoints. Documentation automatique via Swagger. +- **Base de donnĂ©es** : SQLite par dĂ©faut pour la simplicitĂ© et la portabilitĂ© (situĂ©e dans `data/jardin.db`). +- **Media** : Les images uploadĂ©es sont stockĂ©es dans `data/uploads/` et servies via `/uploads`. + +## 📂 Structure des DonnĂ©es (ModĂšles SQLModel) + +- `Garden` : Jardins (nom, dimensions, exposition, gĂ©olocalisation). +- `Plant` : BibliothĂšque de plantes (nom, famille, exigences). +- `Variety` : VariĂ©tĂ©s spĂ©cifiques de plantes. +- `Planting` : Instances de plantation dans un jardin (date, Ă©tat, position). +- `Task` : TĂąches Ă  accomplir (arrosage, taille, etc.). +- `Settings` : ParamĂštres utilisateur (lat/long pour mĂ©tĂ©o/lune). +- `Meteo` : DonnĂ©es mĂ©tĂ©o locales et prĂ©visions. +- `Lunar` : Calculs de phases et conseils lunaires. + +## đŸ€– Service IA + +Le service de dĂ©tection utilise le modĂšle `foduucom/plant-leaf-detection-and-classification` via YOLOv8. +Endpoint : `POST /detect` acceptant une image. +Il est intĂ©grĂ© au backend via le router `identify`. diff --git a/amelioration.md b/amelioration.md index a250424..ad1913a 100644 --- a/amelioration.md +++ b/amelioration.md @@ -81,4 +81,6 @@ bibliotehque photo: backend : - [ ] methode simple pour mettre a jours la base de donnĂ©e ; brainstorming - - [ ] mise a jours bdd via api puis je peut ajouter des script dans mon openclaw] \ No newline at end of file + - [ ] mise a jours bdd via api puis je peut ajouter des script dans mon openclaw] + + - [ ] ajouter des etoiles 1 Ă  5 si j'ai Ă©tĂ© satisfait de la plante \ No newline at end of file diff --git a/avancement.md b/avancement.md index c2d1f58..6e6b776 100644 --- a/avancement.md +++ b/avancement.md @@ -7457,3 +7457,43 @@ You've hit your limit · resets 7pm (Europe/Paris) - Note tests backend: - `pytest backend/tests/test_tools.py` reste bloque dans ce contexte d'execution, mais les changements de schema/code compilent et la colonne DB est presente. + +## Mise a jour Codex - 2026-02-22 (Planning, Settings, Saints/Dictons, Outils) + +### Planning (frontend) +- Fichier: `frontend/src/views/PlanningView.vue` +- Vue planning etendue a 4 semaines (28 jours) +- Navigation par boutons `Prev`, `Today`, `Next` +- Selection d'une case/jour avec panneau "Detail du jour" +- Ajout de marqueurs visuels (petits ronds colores) dans les cases pour signaler les taches non terminees + +### Outils: notice en texte libre +- Fichier: `frontend/src/views/OutilsView.vue` +- Remplacement du champ "notice fichier texte" par une zone de texte (`notice_texte`) +- Affichage de la notice texte sur la carte outil +- Compatibilite conservee pour l'existant (`notice_fichier_url` en fallback) +- Test backend ajoute: + - `backend/tests/test_tools.py::test_tool_with_notice_texte` + +### Settings: backup ZIP + test API backend +- Backend: + - `backend/app/routers/settings.py` + - nouvel endpoint `GET /api/settings/backup/download` + - archive ZIP contenant: base SQLite, uploads (images/videos), fichiers texte utiles, `manifest.json` +- Frontend: + - `frontend/src/api/settings.ts`: `downloadBackup()` + - `frontend/src/views/ReglagesView.vue`: + - bouton "Telecharger la sauvegarde (.zip)" + - section "Test API backend" avec liens rapides: + - `/docs`, `/redoc`, `/api/health` + +### Saints / dictons (hors webapp) +- Dossier: `calendrier_lunaire/saints_dictons/` +- Fichiers JSON generes: + - `saints_du_jour.json` + - `dictons_du_jour.json` +- Scripts ajoutes: + - `export_saints_dictons_json.py` (source `saints_2026.json` -> 2 JSON separes) + - `import_saints_dictons_db.py` (import SQLite `replace`/`append`) +- Import teste sur base temporaire: + - resultat: `366` jours de saints + `366` dictons importes diff --git a/backend/app/migrate.py b/backend/app/migrate.py index 1309615..2348509 100644 --- a/backend/app/migrate.py +++ b/backend/app/migrate.py @@ -40,6 +40,7 @@ EXPECTED_COLUMNS: dict[str, list[tuple[str, str, str | None]]] = { "tool": [ ("photo_url", "TEXT", None), ("video_url", "TEXT", None), + ("notice_texte", "TEXT", None), ("notice_fichier_url", "TEXT", None), ("boutique_nom", "TEXT", None), ("boutique_url", "TEXT", None), diff --git a/backend/app/models/tool.py b/backend/app/models/tool.py index f23a09b..ce236aa 100644 --- a/backend/app/models/tool.py +++ b/backend/app/models/tool.py @@ -10,6 +10,7 @@ class Tool(SQLModel, table=True): categorie: Optional[str] = None # beche|fourche|griffe|arrosage|taille|autre photo_url: Optional[str] = None video_url: Optional[str] = None + notice_texte: Optional[str] = None notice_fichier_url: Optional[str] = None boutique_nom: Optional[str] = None boutique_url: Optional[str] = None diff --git a/backend/app/routers/media.py b/backend/app/routers/media.py index 712baf1..6777597 100644 --- a/backend/app/routers/media.py +++ b/backend/app/routers/media.py @@ -1,8 +1,9 @@ import os +import unicodedata import uuid from typing import List, Optional -from fastapi import APIRouter, Body, Depends, File, HTTPException, Query, UploadFile, status +from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status from pydantic import BaseModel from sqlmodel import Session, select @@ -16,9 +17,91 @@ class MediaPatch(BaseModel): entity_id: Optional[int] = None titre: Optional[str] = None + +CANONICAL_ENTITY_TYPES = { + "jardin", + "plante", + "adventice", + "outil", + "plantation", + "bibliotheque", +} + +ENTITY_TYPE_ALIASES = { + # Canonique + "jardin": "jardin", + "plante": "plante", + "adventice": "adventice", + "outil": "outil", + "plantation": "plantation", + "bibliotheque": "bibliotheque", + # Variantes FR + "jardins": "jardin", + "plantes": "plante", + "adventices": "adventice", + "outils": "outil", + "plantations": "plantation", + "bibliotheques": "bibliotheque", + "bibliotheque_media": "bibliotheque", + # Variantes EN (courantes via API) + "garden": "jardin", + "gardens": "jardin", + "plant": "plante", + "plants": "plante", + "weed": "adventice", + "weeds": "adventice", + "tool": "outil", + "tools": "outil", + "planting": "plantation", + "plantings": "plantation", + "library": "bibliotheque", + "media_library": "bibliotheque", +} + router = APIRouter(tags=["media"]) +def _normalize_token(value: str) -> str: + token = (value or "").strip().lower() + token = unicodedata.normalize("NFKD", token).encode("ascii", "ignore").decode("ascii") + return token.replace("-", "_").replace(" ", "_") + + +def _normalize_entity_type(value: str, *, strict: bool = True) -> str: + token = _normalize_token(value) + canonical = ENTITY_TYPE_ALIASES.get(token, token) + if canonical in CANONICAL_ENTITY_TYPES: + return canonical + if strict: + allowed = ", ".join(sorted(CANONICAL_ENTITY_TYPES)) + raise HTTPException( + status_code=422, + detail=f"entity_type invalide: '{value}'. Valeurs autorisees: {allowed}", + ) + return value + + +def _entity_type_candidates(value: str) -> set[str]: + canonical = _normalize_entity_type(value, strict=True) + candidates = {canonical} + for alias, target in ENTITY_TYPE_ALIASES.items(): + if target == canonical: + candidates.add(alias) + return candidates + + +def _canonicalize_rows(rows: List[Media], session: Session) -> None: + changed = False + for media in rows: + normalized = _normalize_entity_type(media.entity_type, strict=False) + if normalized in CANONICAL_ENTITY_TYPES and normalized != media.entity_type: + media.entity_type = normalized + session.add(media) + changed = True + if changed: + session.commit() + + def _save_webp(data: bytes, max_px: int) -> str: try: from PIL import Image @@ -47,12 +130,12 @@ async def upload_file(file: UploadFile = File(...)): name = _save_webp(data, 1200) thumb = _save_webp(data, 300) return {"url": f"/uploads/{name}", "thumbnail_url": f"/uploads/{thumb}"} - else: - name = f"{uuid.uuid4()}_{file.filename}" - path = os.path.join(UPLOAD_DIR, name) - with open(path, "wb") as f: - f.write(data) - return {"url": f"/uploads/{name}", "thumbnail_url": None} + + name = f"{uuid.uuid4()}_{file.filename}" + path = os.path.join(UPLOAD_DIR, name) + with open(path, "wb") as f: + f.write(data) + return {"url": f"/uploads/{name}", "thumbnail_url": None} @router.get("/media/all", response_model=List[Media]) @@ -63,8 +146,10 @@ def list_all_media( """Retourne tous les mĂ©dias, filtrĂ©s optionnellement par entity_type.""" q = select(Media).order_by(Media.created_at.desc()) if entity_type: - q = q.where(Media.entity_type == entity_type) - return session.exec(q).all() + q = q.where(Media.entity_type.in_(_entity_type_candidates(entity_type))) + rows = session.exec(q).all() + _canonicalize_rows(rows, session) + return rows @router.get("/media", response_model=List[Media]) @@ -73,15 +158,19 @@ def list_media( entity_id: int = Query(...), session: Session = Depends(get_session), ): - return session.exec( + rows = session.exec( select(Media).where( - Media.entity_type == entity_type, Media.entity_id == entity_id + Media.entity_type.in_(_entity_type_candidates(entity_type)), + Media.entity_id == entity_id, ) ).all() + _canonicalize_rows(rows, session) + return rows @router.post("/media", response_model=Media, status_code=status.HTTP_201_CREATED) def create_media(m: Media, session: Session = Depends(get_session)): + m.entity_type = _normalize_entity_type(m.entity_type, strict=True) session.add(m) session.commit() session.refresh(m) @@ -93,7 +182,12 @@ def update_media(id: int, payload: MediaPatch, session: Session = Depends(get_se m = session.get(Media, id) if not m: raise HTTPException(404, "Media introuvable") - for k, v in payload.model_dump(exclude_none=True).items(): + + updates = payload.model_dump(exclude_none=True) + if "entity_type" in updates: + updates["entity_type"] = _normalize_entity_type(updates["entity_type"], strict=True) + + for k, v in updates.items(): setattr(m, k, v) session.add(m) session.commit() diff --git a/backend/app/routers/plants.py b/backend/app/routers/plants.py index 1ebcb1d..3560f93 100644 --- a/backend/app/routers/plants.py +++ b/backend/app/routers/plants.py @@ -12,7 +12,7 @@ def list_plants( categorie: Optional[str] = Query(None), session: Session = Depends(get_session), ): - q = select(Plant) + q = select(Plant).order_by(Plant.nom_commun, Plant.variete, Plant.id) if categorie: q = q.where(Plant.categorie == categorie) return session.exec(q).all() diff --git a/backend/app/routers/settings.py b/backend/app/routers/settings.py index 6be1a92..47b2db3 100644 --- a/backend/app/routers/settings.py +++ b/backend/app/routers/settings.py @@ -1,18 +1,28 @@ import os import shutil import time +import json +import tempfile +import zipfile +from datetime import datetime, timezone +from pathlib import Path from typing import Any from fastapi import APIRouter, Depends +from fastapi.responses import FileResponse +from starlette.background import BackgroundTask from sqlmodel import Session, select from app.database import get_session from app.models.settings import UserSettings -from app.config import UPLOAD_DIR +from app.config import DATABASE_URL, UPLOAD_DIR router = APIRouter(tags=["rĂ©glages"]) _PREV_CPU_USAGE_USEC: int | None = None _PREV_CPU_TS: float | None = None +_TEXT_EXTENSIONS = { + ".txt", ".md", ".markdown", ".json", ".csv", ".log", ".ini", ".yaml", ".yml", ".xml" +} def _read_int_from_paths(paths: list[str]) -> int | None: @@ -113,6 +123,68 @@ def _disk_stats() -> dict[str, Any]: } +def _safe_remove(path: str) -> None: + try: + os.remove(path) + except OSError: + pass + + +def _resolve_sqlite_db_path() -> Path | None: + prefix = "sqlite:///" + if not DATABASE_URL.startswith(prefix): + return None + raw = DATABASE_URL[len(prefix):] + if not raw: + return None + db_path = Path(raw) + if db_path.is_absolute(): + return db_path + return (Path.cwd() / db_path).resolve() + + +def _zip_directory(zipf: zipfile.ZipFile, source_dir: Path, arc_prefix: str) -> int: + count = 0 + if not source_dir.is_dir(): + return count + for root, _, files in os.walk(source_dir): + root_path = Path(root) + for name in files: + file_path = root_path / name + if not file_path.is_file(): + continue + rel = file_path.relative_to(source_dir) + arcname = str(Path(arc_prefix) / rel) + zipf.write(file_path, arcname=arcname) + count += 1 + return count + + +def _zip_data_text_files( + zipf: zipfile.ZipFile, + data_root: Path, + db_path: Path | None, + uploads_dir: Path, +) -> int: + count = 0 + if not data_root.is_dir(): + return count + for root, _, files in os.walk(data_root): + root_path = Path(root) + for name in files: + file_path = root_path / name + if db_path and file_path == db_path: + continue + if uploads_dir in file_path.parents: + continue + if file_path.suffix.lower() not in _TEXT_EXTENSIONS: + continue + rel = file_path.relative_to(data_root) + zipf.write(file_path, arcname=str(Path("data_text") / rel)) + count += 1 + return count + + @router.get("/settings") def get_settings(session: Session = Depends(get_session)): rows = session.exec(select(UserSettings)).all() @@ -161,3 +233,51 @@ def get_debug_system_stats() -> dict[str, Any]: "memory": _memory_stats(), "disk": _disk_stats(), } + + +@router.get("/settings/backup/download") +def download_backup_zip() -> FileResponse: + now = datetime.now(timezone.utc) + ts = now.strftime("%Y%m%d_%H%M%S") + db_path = _resolve_sqlite_db_path() + uploads_dir = Path(UPLOAD_DIR).resolve() + data_root = db_path.parent if db_path else uploads_dir.parent + + fd, tmp_zip_path = tempfile.mkstemp(prefix=f"jardin_backup_{ts}_", suffix=".zip") + os.close(fd) + tmp_zip = Path(tmp_zip_path) + + stats = { + "database_files": 0, + "upload_files": 0, + "text_files": 0, + } + + with zipfile.ZipFile(tmp_zip, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as zipf: + if db_path and db_path.is_file(): + zipf.write(db_path, arcname=f"db/{db_path.name}") + stats["database_files"] = 1 + + stats["upload_files"] = _zip_directory(zipf, uploads_dir, "uploads") + stats["text_files"] = _zip_data_text_files(zipf, data_root, db_path, uploads_dir) + + manifest = { + "generated_at_utc": now.isoformat(), + "database_url": DATABASE_URL, + "paths": { + "database_path": str(db_path) if db_path else None, + "uploads_path": str(uploads_dir), + "data_root": str(data_root), + }, + "included": stats, + "text_extensions": sorted(_TEXT_EXTENSIONS), + } + zipf.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2)) + + download_name = f"jardin_backup_{ts}.zip" + return FileResponse( + path=str(tmp_zip), + media_type="application/zip", + filename=download_name, + background=BackgroundTask(_safe_remove, str(tmp_zip)), + ) diff --git a/backend/app/routers/tasks.py b/backend/app/routers/tasks.py index 1d15af4..fe4c96e 100644 --- a/backend/app/routers/tasks.py +++ b/backend/app/routers/tasks.py @@ -12,6 +12,7 @@ router = APIRouter(tags=["tĂąches"]) def list_tasks( statut: Optional[str] = None, garden_id: Optional[int] = None, + planting_id: Optional[int] = None, session: Session = Depends(get_session), ): q = select(Task) @@ -19,6 +20,9 @@ def list_tasks( q = q.where(Task.statut == statut) if garden_id: q = q.where(Task.garden_id == garden_id) + if planting_id: + q = q.where(Task.planting_id == planting_id) + q = q.order_by(Task.echeance, Task.created_at.desc()) return session.exec(q).all() diff --git a/backend/tests/test_media_aliases.py b/backend/tests/test_media_aliases.py new file mode 100644 index 0000000..0c3853f --- /dev/null +++ b/backend/tests/test_media_aliases.py @@ -0,0 +1,26 @@ +def test_create_media_normalizes_english_entity_type(client): + r = client.post( + "/api/media", + json={ + "entity_type": "plant", + "entity_id": 12, + "url": "/uploads/test.webp", + }, + ) + assert r.status_code == 201 + assert r.json()["entity_type"] == "plante" + + +def test_list_media_accepts_alias_entity_type_filter(client): + client.post( + "/api/media", + json={ + "entity_type": "plante", + "entity_id": 99, + "url": "/uploads/test2.webp", + }, + ) + r = client.get("/api/media", params={"entity_type": "plant", "entity_id": 99}) + assert r.status_code == 200 + assert len(r.json()) == 1 + assert r.json()[0]["entity_type"] == "plante" diff --git a/backend/tests/test_plants.py b/backend/tests/test_plants.py index 6c16c3e..3b47afd 100644 --- a/backend/tests/test_plants.py +++ b/backend/tests/test_plants.py @@ -12,6 +12,16 @@ def test_list_plants(client): assert len(r.json()) == 2 +def test_allow_same_common_name_with_different_varieties(client): + client.post("/api/plants", json={"nom_commun": "Tomate", "variete": "Roma"}) + client.post("/api/plants", json={"nom_commun": "Tomate", "variete": "Andine Cornue"}) + r = client.get("/api/plants") + assert r.status_code == 200 + tomates = [p for p in r.json() if p["nom_commun"] == "Tomate"] + assert len(tomates) == 2 + assert {p.get("variete") for p in tomates} == {"Roma", "Andine Cornue"} + + def test_get_plant(client): r = client.post("/api/plants", json={"nom_commun": "Basilic"}) id = r.json()["id"] diff --git a/backend/tests/test_tasks.py b/backend/tests/test_tasks.py index 8eb7c7d..08c9827 100644 --- a/backend/tests/test_tasks.py +++ b/backend/tests/test_tasks.py @@ -26,3 +26,13 @@ def test_update_task_statut(client): r2 = client.put(f"/api/tasks/{id}", json={"titre": "À faire", "statut": "fait"}) assert r2.status_code == 200 assert r2.json()["statut"] == "fait" + + +def test_filter_tasks_by_planting_id(client): + client.post("/api/tasks", json={"titre": "Template arrosage", "statut": "template"}) + client.post("/api/tasks", json={"titre": "Arroser rang 1", "statut": "a_faire", "planting_id": 10}) + client.post("/api/tasks", json={"titre": "Arroser rang 2", "statut": "a_faire", "planting_id": 11}) + r = client.get("/api/tasks?planting_id=10") + assert r.status_code == 200 + assert len(r.json()) == 1 + assert r.json()[0]["planting_id"] == 10 diff --git a/backend/tests/test_tools.py b/backend/tests/test_tools.py index 3b673d3..ba6895b 100644 --- a/backend/tests/test_tools.py +++ b/backend/tests/test_tools.py @@ -28,3 +28,15 @@ def test_tool_with_video_url(client): ) assert r.status_code == 201 assert r.json()["video_url"] == "/uploads/demo-outil.mp4" + + +def test_tool_with_notice_texte(client): + r = client.post( + "/api/tools", + json={ + "nom": "SĂ©cateur", + "notice_texte": "Aiguiser la lame tous les 3 mois.", + }, + ) + assert r.status_code == 201 + assert r.json()["notice_texte"] == "Aiguiser la lame tous les 3 mois." diff --git a/codex.md b/codex.md index c78f006..075c79b 100644 --- a/codex.md +++ b/codex.md @@ -35,12 +35,21 @@ Dossier dĂ©diĂ©: `calendrier_lunaire/saints_dictons/` - `calendrier_lunaire/saints_dictons/saint_dicton_year_scraper.py` - Exemple de sortie: - `calendrier_lunaire/saints_dictons/saints_2026.json` +- Exports JSON sĂ©parĂ©s: + - `calendrier_lunaire/saints_dictons/saints_du_jour.json` + - `calendrier_lunaire/saints_dictons/dictons_du_jour.json` +- Scripts hors webapp: + - `calendrier_lunaire/saints_dictons/export_saints_dictons_json.py` + - `calendrier_lunaire/saints_dictons/import_saints_dictons_db.py` ### Fonctions/Ă©volutions intĂ©grĂ©es - Format JSON cible: `date`, `saints[]`, `dictons[]` - Support de formats de date multiples - Ajout de logs de progression dans le scraper - Enregistrement JSON (pas uniquement affichage terminal) +- GĂ©nĂ©ration de 2 jeux de donnĂ©es dĂ©diĂ©s (saints / dictons) +- Import automatisĂ© en SQLite (`replace` ou `append`) +- CrĂ©ation table `saint_du_jour` si absente + alimentation table `dicton` ## 3) PrĂ©visions mĂ©tĂ©o Open-Meteo @@ -103,3 +112,26 @@ Dossier: `test_yolo/` - Plan d'amĂ©lioration: `amelioration.md` - Plan mĂ©tĂ©o/astuces: `avancement.md` (contient plan + logs de session) +## 8) Webapp - Ă©volutions rĂ©centes + +### Planning +- `frontend/src/views/PlanningView.vue` +- Passage en vue 4 semaines (28 jours) +- Navigation par pĂ©riode: `Prev`, `Today`, `Next` +- SĂ©lection d'un jour avec panneau "DĂ©tail du jour" +- Marqueurs visuels par tĂąches non terminĂ©es (ronds colorĂ©s par prioritĂ©) + +### Outils +- `frontend/src/views/OutilsView.vue` +- Le champ notice est dĂ©sormais une zone de texte libre (`notice_texte`) +- Conserve compatibilitĂ© lecture des anciennes notices fichier (`notice_fichier_url`) +- Test backend ajoutĂ©: `backend/tests/test_tools.py::test_tool_with_notice_texte` + +### RĂ©glages +- `backend/app/routers/settings.py` +- `frontend/src/views/ReglagesView.vue` +- Sauvegarde ZIP tĂ©lĂ©chargeable (BDD + uploads + fichiers texte + manifeste) +- Liens rapides de test API backend: + - Swagger: `/docs` + - ReDoc: `/redoc` + - SantĂ©: `/api/health` diff --git a/data/.gitkeep b/data/.gitkeep old mode 100644 new mode 100755 diff --git a/data/jardin.db b/data/jardin.db index 1390c08..5057da4 100755 Binary files a/data/jardin.db and b/data/jardin.db differ diff --git a/data/jardin.db-shm b/data/jardin.db-shm new file mode 100755 index 0000000..60b7eac Binary files /dev/null and b/data/jardin.db-shm differ diff --git a/data/jardin.db-wal b/data/jardin.db-wal new file mode 100755 index 0000000..9ceaf03 Binary files /dev/null and b/data/jardin.db-wal differ diff --git a/data/meteo_cache.json b/data/meteo_cache.json old mode 100644 new mode 100755 diff --git a/data/uploads/00da2c3c-6e95-413d-910a-c1dad0160479.webp b/data/uploads/00da2c3c-6e95-413d-910a-c1dad0160479.webp old mode 100644 new mode 100755 diff --git a/data/uploads/074d138e-62e8-4099-99f8-c19f40aed9be.webp b/data/uploads/074d138e-62e8-4099-99f8-c19f40aed9be.webp old mode 100644 new mode 100755 diff --git a/data/uploads/09ddf03f-43c5-42c4-99b6-db444cda349c.webp b/data/uploads/09ddf03f-43c5-42c4-99b6-db444cda349c.webp old mode 100644 new mode 100755 diff --git a/data/uploads/0ea0948d-85e8-45a5-b415-77e75b884d2a.webp b/data/uploads/0ea0948d-85e8-45a5-b415-77e75b884d2a.webp old mode 100644 new mode 100755 diff --git a/data/uploads/10a02dde-1e67-48f9-a53a-79c2e7b9d54c.webp b/data/uploads/10a02dde-1e67-48f9-a53a-79c2e7b9d54c.webp old mode 100644 new mode 100755 diff --git a/data/uploads/12c34180-001f-405a-8c0a-e926608c411b.webp b/data/uploads/12c34180-001f-405a-8c0a-e926608c411b.webp old mode 100644 new mode 100755 diff --git a/data/uploads/14390de7-32b4-495a-83b6-7ea198e2f719.webp b/data/uploads/14390de7-32b4-495a-83b6-7ea198e2f719.webp old mode 100644 new mode 100755 diff --git a/data/uploads/18b89ba4-7fac-4094-a12d-67d1eadfbc86.webp b/data/uploads/18b89ba4-7fac-4094-a12d-67d1eadfbc86.webp old mode 100644 new mode 100755 diff --git a/data/uploads/1b0cd239-b6b0-4ae0-ad40-e8bb28f32171.webp b/data/uploads/1b0cd239-b6b0-4ae0-ad40-e8bb28f32171.webp old mode 100644 new mode 100755 diff --git a/data/uploads/20891c18-13f3-4cc9-8923-fd097dad8824.webp b/data/uploads/20891c18-13f3-4cc9-8923-fd097dad8824.webp old mode 100644 new mode 100755 diff --git a/data/uploads/226a6c43-040c-46cb-b238-e4ba458fd894.webp b/data/uploads/226a6c43-040c-46cb-b238-e4ba458fd894.webp old mode 100644 new mode 100755 diff --git a/data/uploads/232cc375-fac6-4bdd-a4a9-d4316a1d0e52.webp b/data/uploads/232cc375-fac6-4bdd-a4a9-d4316a1d0e52.webp old mode 100644 new mode 100755 diff --git a/data/uploads/23b266e1-543c-4919-93cf-3a44861dafcd.webp b/data/uploads/23b266e1-543c-4919-93cf-3a44861dafcd.webp old mode 100644 new mode 100755 diff --git a/data/uploads/2749da16-ebef-4df0-b03f-0905468b1a08.webp b/data/uploads/2749da16-ebef-4df0-b03f-0905468b1a08.webp new file mode 100644 index 0000000..a6e246a Binary files /dev/null and b/data/uploads/2749da16-ebef-4df0-b03f-0905468b1a08.webp differ diff --git a/data/uploads/2d57019d-a23f-4838-8076-e856b81074a9.webp b/data/uploads/2d57019d-a23f-4838-8076-e856b81074a9.webp old mode 100644 new mode 100755 diff --git a/data/uploads/320d4c8f-1893-4f93-adbd-287bd3f4f4c0.webp b/data/uploads/320d4c8f-1893-4f93-adbd-287bd3f4f4c0.webp old mode 100644 new mode 100755 diff --git a/data/uploads/3241e492-bf06-4741-97d8-4d5e8e63bc4c_PrĂ©parer du purin ortie.mp4 b/data/uploads/3241e492-bf06-4741-97d8-4d5e8e63bc4c_PrĂ©parer du purin ortie.mp4 old mode 100644 new mode 100755 diff --git a/data/uploads/379c8ae9-1b7f-4558-8553-224fcb2d854a.webp b/data/uploads/379c8ae9-1b7f-4558-8553-224fcb2d854a.webp old mode 100644 new mode 100755 diff --git a/data/uploads/384670c3-e3bc-4b30-af94-6a7f0fd0a0a9.webp b/data/uploads/384670c3-e3bc-4b30-af94-6a7f0fd0a0a9.webp old mode 100644 new mode 100755 diff --git a/data/uploads/39aa7d0c-960e-4ad5-a60e-2808599b6442.webp b/data/uploads/39aa7d0c-960e-4ad5-a60e-2808599b6442.webp old mode 100644 new mode 100755 diff --git a/data/uploads/3eb65012-b80d-456d-8e88-cc2b31c5d303.webp b/data/uploads/3eb65012-b80d-456d-8e88-cc2b31c5d303.webp old mode 100644 new mode 100755 diff --git a/data/uploads/43ed8d77-3001-4f47-9e89-90dd78a488c7.webp b/data/uploads/43ed8d77-3001-4f47-9e89-90dd78a488c7.webp old mode 100644 new mode 100755 diff --git a/data/uploads/5298079b-daa3-491f-9bf6-d7b025fd322e.webp b/data/uploads/5298079b-daa3-491f-9bf6-d7b025fd322e.webp old mode 100644 new mode 100755 diff --git a/data/uploads/59728bc4-e114-4c8b-8917-9fe7cab61b43.webp b/data/uploads/59728bc4-e114-4c8b-8917-9fe7cab61b43.webp old mode 100644 new mode 100755 diff --git a/data/uploads/5ff6ba79-89d4-46de-9476-87f3a9f7ef8b.webp b/data/uploads/5ff6ba79-89d4-46de-9476-87f3a9f7ef8b.webp new file mode 100755 index 0000000..fecf509 Binary files /dev/null and b/data/uploads/5ff6ba79-89d4-46de-9476-87f3a9f7ef8b.webp differ diff --git a/data/uploads/6a099c4e-2f56-4747-b85f-1ad8ed369d6a.webp b/data/uploads/6a099c4e-2f56-4747-b85f-1ad8ed369d6a.webp old mode 100644 new mode 100755 diff --git a/data/uploads/6a5a8bde-3cb3-4a70-8d0b-20330ab3bc8f_JE TEST L'EMIETTEUR LEBORGNE.mp4 b/data/uploads/6a5a8bde-3cb3-4a70-8d0b-20330ab3bc8f_JE TEST L'EMIETTEUR LEBORGNE.mp4 old mode 100644 new mode 100755 diff --git a/data/uploads/71f62a1e-de70-405c-b330-f25344243a3b.webp b/data/uploads/71f62a1e-de70-405c-b330-f25344243a3b.webp old mode 100644 new mode 100755 diff --git a/data/uploads/7dfa2f10-5a5f-4d7b-b2a4-ceaa309d7764.webp b/data/uploads/7dfa2f10-5a5f-4d7b-b2a4-ceaa309d7764.webp old mode 100644 new mode 100755 diff --git a/data/uploads/7f71435a-a28c-4ff1-91f9-fbf0a442b6b0.webp b/data/uploads/7f71435a-a28c-4ff1-91f9-fbf0a442b6b0.webp new file mode 100644 index 0000000..a6e246a Binary files /dev/null and b/data/uploads/7f71435a-a28c-4ff1-91f9-fbf0a442b6b0.webp differ diff --git a/data/uploads/7fc78624-0b20-4a45-a1d4-df70681c9cd8.webp b/data/uploads/7fc78624-0b20-4a45-a1d4-df70681c9cd8.webp old mode 100644 new mode 100755 diff --git a/data/uploads/8850c422-2dfc-4b38-be82-c5a5197b6f4f.webp b/data/uploads/8850c422-2dfc-4b38-be82-c5a5197b6f4f.webp old mode 100644 new mode 100755 diff --git a/data/uploads/8913cdc5-363a-4b00-8af0-1fc62eede9f6.webp b/data/uploads/8913cdc5-363a-4b00-8af0-1fc62eede9f6.webp new file mode 100644 index 0000000..a6e246a Binary files /dev/null and b/data/uploads/8913cdc5-363a-4b00-8af0-1fc62eede9f6.webp differ diff --git a/data/uploads/8a7d347c-b3b1-42c5-b1ab-8699ee52e87d.webp b/data/uploads/8a7d347c-b3b1-42c5-b1ab-8699ee52e87d.webp new file mode 100644 index 0000000..a6e246a Binary files /dev/null and b/data/uploads/8a7d347c-b3b1-42c5-b1ab-8699ee52e87d.webp differ diff --git a/data/uploads/8e5d9fa3-9f6f-4abb-99fa-47e8f8e13246.webp b/data/uploads/8e5d9fa3-9f6f-4abb-99fa-47e8f8e13246.webp old mode 100644 new mode 100755 diff --git a/data/uploads/91dce188-d312-4188-8595-6c36c276af95.webp b/data/uploads/91dce188-d312-4188-8595-6c36c276af95.webp old mode 100644 new mode 100755 diff --git a/data/uploads/938ec699-e38f-47ca-aba9-a936fecddb72.webp b/data/uploads/938ec699-e38f-47ca-aba9-a936fecddb72.webp old mode 100644 new mode 100755 diff --git a/data/uploads/976a8dd2-ad59-45bb-9171-09041d15bb0c.webp b/data/uploads/976a8dd2-ad59-45bb-9171-09041d15bb0c.webp old mode 100644 new mode 100755 diff --git a/data/uploads/9e0d42f8-7dff-47e1-b7e1-b234a2688665.webp b/data/uploads/9e0d42f8-7dff-47e1-b7e1-b234a2688665.webp old mode 100644 new mode 100755 diff --git a/data/uploads/a00759fa-ecee-472e-bfa6-a9b6f90d4c84.webp b/data/uploads/a00759fa-ecee-472e-bfa6-a9b6f90d4c84.webp new file mode 100644 index 0000000..a6e246a Binary files /dev/null and b/data/uploads/a00759fa-ecee-472e-bfa6-a9b6f90d4c84.webp differ diff --git a/data/uploads/a3c70da5-8081-42c0-ba14-05b936780fb6.webp b/data/uploads/a3c70da5-8081-42c0-ba14-05b936780fb6.webp old mode 100644 new mode 100755 diff --git a/data/uploads/a685709b-d3bc-4374-a696-9b97eda89acd.webp b/data/uploads/a685709b-d3bc-4374-a696-9b97eda89acd.webp old mode 100644 new mode 100755 diff --git a/data/uploads/a866dfad-6d98-46e8-8bee-0236841d681a.webp b/data/uploads/a866dfad-6d98-46e8-8bee-0236841d681a.webp old mode 100644 new mode 100755 diff --git a/data/uploads/a9806ada-62c6-47c6-8af2-317b4bc54141.webp b/data/uploads/a9806ada-62c6-47c6-8af2-317b4bc54141.webp old mode 100644 new mode 100755 diff --git a/data/uploads/ad0364aa-8bc3-4634-8269-adceed8b31bf.webp b/data/uploads/ad0364aa-8bc3-4634-8269-adceed8b31bf.webp old mode 100644 new mode 100755 diff --git a/data/uploads/afeff484-7a2b-4684-8786-9c894d89d899.webp b/data/uploads/afeff484-7a2b-4684-8786-9c894d89d899.webp old mode 100644 new mode 100755 diff --git a/data/uploads/b06a46d3-7335-4ec2-8c5d-6fc72cb19992.webp b/data/uploads/b06a46d3-7335-4ec2-8c5d-6fc72cb19992.webp old mode 100644 new mode 100755 diff --git a/data/uploads/b1d4c0af-c9ed-45bf-a8dd-7a2f78f1b609_PrĂ©parer du purin ortie.mp4 b/data/uploads/b1d4c0af-c9ed-45bf-a8dd-7a2f78f1b609_PrĂ©parer du purin ortie.mp4 old mode 100644 new mode 100755 diff --git a/data/uploads/b3c55e0a-86ce-4f79-9ec0-1bf3d14cace5.webp b/data/uploads/b3c55e0a-86ce-4f79-9ec0-1bf3d14cace5.webp old mode 100644 new mode 100755 diff --git a/data/uploads/b4215107-40e8-4ca7-ad97-70d16d69815c.webp b/data/uploads/b4215107-40e8-4ca7-ad97-70d16d69815c.webp old mode 100644 new mode 100755 diff --git a/data/uploads/b9a92615-069c-49ff-8cda-a2e059299777.webp b/data/uploads/b9a92615-069c-49ff-8cda-a2e059299777.webp old mode 100644 new mode 100755 diff --git a/data/uploads/bc3a8bb6-93ae-4b7f-92ea-770617af2851.webp b/data/uploads/bc3a8bb6-93ae-4b7f-92ea-770617af2851.webp old mode 100644 new mode 100755 diff --git a/data/uploads/c0af61f6-469e-404f-b6fe-de07a8ec5b1b.webp b/data/uploads/c0af61f6-469e-404f-b6fe-de07a8ec5b1b.webp old mode 100644 new mode 100755 diff --git a/data/uploads/c657085f-0929-425c-9c33-51e1534226c3.webp b/data/uploads/c657085f-0929-425c-9c33-51e1534226c3.webp old mode 100644 new mode 100755 diff --git a/data/uploads/c755dd26-d0c4-425a-8d36-121bed910af2.webp b/data/uploads/c755dd26-d0c4-425a-8d36-121bed910af2.webp old mode 100644 new mode 100755 diff --git a/data/uploads/c9a94885-f5ac-490e-8b5d-458e60df84c4.webp b/data/uploads/c9a94885-f5ac-490e-8b5d-458e60df84c4.webp new file mode 100644 index 0000000..a6e246a Binary files /dev/null and b/data/uploads/c9a94885-f5ac-490e-8b5d-458e60df84c4.webp differ diff --git a/data/uploads/ca536790-ee67-43c1-809e-80c8a27b2732.webp b/data/uploads/ca536790-ee67-43c1-809e-80c8a27b2732.webp old mode 100644 new mode 100755 diff --git a/data/uploads/cac349a1-965c-495f-8b24-476fd46657dc.webp b/data/uploads/cac349a1-965c-495f-8b24-476fd46657dc.webp old mode 100644 new mode 100755 diff --git a/data/uploads/cbcf5f2d-6ede-494e-b1e7-0c46f93db408.webp b/data/uploads/cbcf5f2d-6ede-494e-b1e7-0c46f93db408.webp old mode 100644 new mode 100755 diff --git a/data/uploads/cdf75243-4d21-47e9-8ab2-e05588e50345.webp b/data/uploads/cdf75243-4d21-47e9-8ab2-e05588e50345.webp new file mode 100644 index 0000000..a6e246a Binary files /dev/null and b/data/uploads/cdf75243-4d21-47e9-8ab2-e05588e50345.webp differ diff --git a/data/uploads/ce04b400-ad9a-4b19-8b7d-a0d3744338d6.webp b/data/uploads/ce04b400-ad9a-4b19-8b7d-a0d3744338d6.webp old mode 100644 new mode 100755 diff --git a/data/uploads/d1767f2e-5155-4932-bd15-27f1be7eb597.webp b/data/uploads/d1767f2e-5155-4932-bd15-27f1be7eb597.webp old mode 100644 new mode 100755 diff --git a/data/uploads/d2be77c3-5b5a-427f-9ca4-0cdfca412707.webp b/data/uploads/d2be77c3-5b5a-427f-9ca4-0cdfca412707.webp old mode 100644 new mode 100755 diff --git a/data/uploads/d2be8f33-14e7-4770-bce8-ac467c645933_upload-test-tSW7.bin b/data/uploads/d2be8f33-14e7-4770-bce8-ac467c645933_upload-test-tSW7.bin old mode 100644 new mode 100755 diff --git a/data/uploads/d37feb85-c442-44f4-8c74-0d7b583f5d29.webp b/data/uploads/d37feb85-c442-44f4-8c74-0d7b583f5d29.webp old mode 100644 new mode 100755 diff --git a/data/uploads/d6a2275e-e3bd-4ea2-b39b-6be2fb8c9aa3.webp b/data/uploads/d6a2275e-e3bd-4ea2-b39b-6be2fb8c9aa3.webp old mode 100644 new mode 100755 diff --git a/data/uploads/d6cb8815-fc3b-4fbe-af9f-64334d46c1a7.webp b/data/uploads/d6cb8815-fc3b-4fbe-af9f-64334d46c1a7.webp old mode 100644 new mode 100755 diff --git a/data/uploads/d7f1a775-2173-4411-ad58-949e5fdd7385.webp b/data/uploads/d7f1a775-2173-4411-ad58-949e5fdd7385.webp old mode 100644 new mode 100755 diff --git a/data/uploads/d8b40e07-2a86-4b7c-a67a-82cab6c2f538.webp b/data/uploads/d8b40e07-2a86-4b7c-a67a-82cab6c2f538.webp old mode 100644 new mode 100755 diff --git a/data/uploads/db1e6f9d-ab3a-4ba5-802f-79a7d375e7fd.webp b/data/uploads/db1e6f9d-ab3a-4ba5-802f-79a7d375e7fd.webp old mode 100644 new mode 100755 diff --git a/data/uploads/dfcfaae4-f640-476f-ba0c-21ad4e9e7642_PrĂ©parer du purin ortie.f605.mp4 b/data/uploads/dfcfaae4-f640-476f-ba0c-21ad4e9e7642_PrĂ©parer du purin ortie.f605.mp4 deleted file mode 100644 index 1427c6a..0000000 Binary files a/data/uploads/dfcfaae4-f640-476f-ba0c-21ad4e9e7642_PrĂ©parer du purin ortie.f605.mp4 and /dev/null differ diff --git a/data/uploads/e25ce267-8588-40d4-b77f-250eeab2c543.webp b/data/uploads/e25ce267-8588-40d4-b77f-250eeab2c543.webp old mode 100644 new mode 100755 diff --git a/data/uploads/e6e1b050-8dfb-45b0-b559-4211c995a3af.webp b/data/uploads/e6e1b050-8dfb-45b0-b559-4211c995a3af.webp old mode 100644 new mode 100755 diff --git a/data/uploads/e93604a3-80a9-493d-93bd-5e87141bf62a_upload-test-NFmT.bin b/data/uploads/e93604a3-80a9-493d-93bd-5e87141bf62a_upload-test-NFmT.bin old mode 100644 new mode 100755 diff --git a/data/uploads/eb6dd691-b270-4b82-9951-8090a2b6ab6f.webp b/data/uploads/eb6dd691-b270-4b82-9951-8090a2b6ab6f.webp old mode 100644 new mode 100755 diff --git a/data/uploads/ec1bc318-9fac-45e7-b4f9-b0486dd9bb38.webp b/data/uploads/ec1bc318-9fac-45e7-b4f9-b0486dd9bb38.webp old mode 100644 new mode 100755 diff --git a/data/uploads/ec61e366-2221-48f6-bc21-c5eed0fb321c.webp b/data/uploads/ec61e366-2221-48f6-bc21-c5eed0fb321c.webp old mode 100644 new mode 100755 diff --git a/data/uploads/ed604ecc-771d-4b0f-9117-670c83a659a3.webp b/data/uploads/ed604ecc-771d-4b0f-9117-670c83a659a3.webp old mode 100644 new mode 100755 diff --git a/data/uploads/ee58dbb6-0c43-4f93-8141-55071d229b10.webp b/data/uploads/ee58dbb6-0c43-4f93-8141-55071d229b10.webp new file mode 100644 index 0000000..a6e246a Binary files /dev/null and b/data/uploads/ee58dbb6-0c43-4f93-8141-55071d229b10.webp differ diff --git a/data/uploads/ef9de9be-b78b-4a52-9a59-d8ca27a8786b.webp b/data/uploads/ef9de9be-b78b-4a52-9a59-d8ca27a8786b.webp old mode 100644 new mode 100755 diff --git a/data/uploads/f5cfb3a7-919f-468a-bf3a-04c77061d93c.webp b/data/uploads/f5cfb3a7-919f-468a-bf3a-04c77061d93c.webp old mode 100644 new mode 100755 diff --git a/data/uploads/f64273e3-0abe-457c-a642-8c567b756471.webp b/data/uploads/f64273e3-0abe-457c-a642-8c567b756471.webp old mode 100644 new mode 100755 diff --git a/data/uploads/f9039934-2c37-455e-8a00-573606ad06e1.webp b/data/uploads/f9039934-2c37-455e-8a00-573606ad06e1.webp old mode 100644 new mode 100755 diff --git a/data/uploads/fb38bcbf-1c63-4070-b662-20caa13b8048.webp b/data/uploads/fb38bcbf-1c63-4070-b662-20caa13b8048.webp new file mode 100755 index 0000000..ac53c45 Binary files /dev/null and b/data/uploads/fb38bcbf-1c63-4070-b662-20caa13b8048.webp differ diff --git a/data/uploads/fe39e585-7f19-4864-bfa6-77910cc1de64.webp b/data/uploads/fe39e585-7f19-4864-bfa6-77910cc1de64.webp old mode 100644 new mode 100755 diff --git a/data/uploads/ffc4b9b8-3b33-49fa-9d62-2c6929caa3fe.webp b/data/uploads/ffc4b9b8-3b33-49fa-9d62-2c6929caa3fe.webp old mode 100644 new mode 100755 diff --git a/data/uploads/garden_70757b2d-a485-44f1-881d-322640ef3f9d.webp b/data/uploads/garden_70757b2d-a485-44f1-881d-322640ef3f9d.webp old mode 100644 new mode 100755 diff --git a/data/uploads/garden_d249cc32-f10d-4e1f-a99c-e1b2deecfab1.webp b/data/uploads/garden_d249cc32-f10d-4e1f-a99c-e1b2deecfab1.webp old mode 100644 new mode 100755 diff --git a/data/uploads/garden_e3dc6e2e-9fd1-4088-9c53-4edd7ceff418.webp b/data/uploads/garden_e3dc6e2e-9fd1-4088-9c53-4edd7ceff418.webp old mode 100644 new mode 100755 diff --git a/docs/api_utilisation_reseau_local_openclaw.md b/docs/api_utilisation_reseau_local_openclaw.md index 9490538..2539c77 100644 --- a/docs/api_utilisation_reseau_local_openclaw.md +++ b/docs/api_utilisation_reseau_local_openclaw.md @@ -96,6 +96,13 @@ curl -X POST http://192.168.1.50:8060/api/upload \ -F "file=@/chemin/fichier.mp4" ``` +TĂ©lĂ©charger une sauvegarde ZIP (BDD + uploads + fichiers texte): + +```bash +curl -L "http://192.168.1.50:8060/api/settings/backup/download" \ + -o "jardin_backup.zip" +``` + ## 4. Scripts de mise Ă  jour BDD (hors webapp) Station locale -> DB: @@ -118,7 +125,26 @@ python3 station_meteo/update_openmeteo_history_db.py --start-date 2026-01-01 --e - Corps JSON: UTF-8 - Pour upload: `multipart/form-data` -## 6. SĂ©curitĂ© (important) +## 6. Skill OpenClaw recommandĂ© + +Le skill Ă  utiliser s'appelle dĂ©sormais: `jardin-api-backend`. + +Emplacement dans le repo: + +- `skills/jardin-api-backend/SKILL.md` +- `skills/jardin-api-backend/references/backend-api-recipes.md` +- `skills/jardin-api-backend/consigne.md` + +Installation rapide (exemple): + +```bash +mkdir -p ~/.codex/skills +cp -R skills/jardin-api-backend ~/.codex/skills/ +``` + +Ensuite, relancer OpenClaw et vĂ©rifier que le skill **Jardin API Backend** apparaĂźt. + +## 7. SĂ©curitĂ© (important) L'API actuelle est sans authentification. Sur rĂ©seau local, minimum recommandĂ©: - segmenter le rĂ©seau (VLAN/VM dĂ©diĂ©es) @@ -126,9 +152,8 @@ L'API actuelle est sans authentification. Sur rĂ©seau local, minimum recommandĂ© - ne pas exposer directement sur Internet - idĂ©alement: reverse proxy + authentification (Basic/Auth token) ou VPN -## 7. Swagger +## 8. Swagger Documentation interactive: - `http://:8060/docs` - diff --git a/docs/plans/2026-02-22-ameliorations-sprint.md b/docs/plans/2026-02-22-ameliorations-sprint.md new file mode 100644 index 0000000..e90f3d1 --- /dev/null +++ b/docs/plans/2026-02-22-ameliorations-sprint.md @@ -0,0 +1,1116 @@ +# AmĂ©liorations Sprint — Jardin App + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** AmĂ©liorer l'UI (settings taille/responsive), nettoyer la BDD, ajouter 404, sĂ©lection cases grille pour plantation, astuce du jour, mĂ©dias galerie, export JSON, observations frontend. + +**Architecture:** Frontend Vue 3 + Tailwind (CSS vars pour tailles), backend FastAPI + SQLModel + SQLite. Les settings de taille sont stockĂ©s dans `user_settings` (table clĂ©-valeur) et appliquĂ©s via des variables CSS sur `:root` dans App.vue au chargement. + +**Tech Stack:** Vue 3 TypeScript, Tailwind CSS, FastAPI, SQLModel, SQLite, Docker Compose + +--- + +## Contexte du projet + +- Backend: `/home/gilles/Documents/vscode/jardin/backend/` sur port 8060 (Docker) +- Frontend: `/home/gilles/Documents/vscode/jardin/frontend/` sur port 8061 (Docker) +- DB: `/home/gilles/Documents/vscode/jardin/data/jardin.db` +- Rebuild backend: `docker compose build backend && docker compose up -d backend` (depuis la racine du projet) +- Rebuild frontend: `docker compose build frontend && docker compose up -d frontend` +- Les `.vue` et `.ts` sont dans `frontend/src/` +- Table `user_settings` : colonnes `cle` (str, PK), `valeur` (str) +- API settings: `GET /api/settings` → `{cle: valeur, ...}` / `PUT /api/settings` → body `{cle: valeur, ...}` +- ThĂšme Gruvbox Dark: bg=#282828, bg-soft=#3c3836, bg-hard=#1d2021, text=#ebdbb2, green=#b8bb26, etc. + +--- + +## Task 1: Settings UI — ParamĂštres de taille d'interface + +**PrioritĂ©:** Haute (demandĂ© explicitement) + +**Files:** +- Modify: `frontend/src/views/ReglagesView.vue` +- Modify: `frontend/src/App.vue` + +**ParamĂštres Ă  ajouter (clĂ©s dans user_settings):** +- `ui_font_size` : taille texte gĂ©nĂ©ral (12–20px, dĂ©faut 14) +- `ui_menu_font_size` : taille texte menu latĂ©ral (11–18px, dĂ©faut 13) +- `ui_menu_icon_size` : taille icĂŽnes menu (14–28px, dĂ©faut 18) +- `ui_thumb_size` : taille miniatures images/vidĂ©os (60–200px, dĂ©faut 96) + +**Step 1: Modifier ReglagesView.vue — ajouter section Interface** + +Ajouter une nouvelle section avant la section "GĂ©nĂ©ral" dans `ReglagesView.vue` : + +```vue +
+

Interface

+

Ajustez les tailles d'affichage. Les changements sont appliqués instantanément.

+ +
+
+ + + {{ uiSizes[s.key] }}{{ s.unit }} +
+
+ +
+ + + {{ uiSavedMsg }} +
+
+``` + +Dans ` +``` + +**Step 2: Ajouter route catch-all dans router/index.ts** + +Ajouter en derniĂšre position dans le tableau `routes` : +```typescript +{ path: '/:pathMatch(.*)*', component: () => import('@/views/NotFoundView.vue') }, +``` + +**Step 3: Rebuild et tester** + +```bash +docker compose build frontend && docker compose up -d frontend +``` + +Aller sur http://10.0.1.109:8061/page-inexistante → vĂ©rifier 404 page. + +**Step 4: Commit** + +```bash +git add frontend/src/views/NotFoundView.vue frontend/src/router/index.ts +git commit -m "feat(frontend): page 404 avec redirect vers dashboard" +``` + +--- + +## Task 5: B3 — Populer la table lunarcalendarentry + +**PrioritĂ©:** Haute + +**Contexte:** La table `lunarcalendarentry` existe (0 lignes). Le service lunar est dans `backend/app/services/lunar.py` et retourne les donnĂ©es par mois via `/api/lunar?month=YYYY-MM`. + +**Files:** +- Read first: `backend/app/routers/lunar.py` +- Read first: `backend/app/models/settings.py` (pour voir LunarCalendarEntry) +- Modify: `backend/app/routers/lunar.py` (auto-persist after compute) + +**Step 1: Lire les fichiers concernĂ©s** + +```bash +cat /home/gilles/Documents/vscode/jardin/backend/app/routers/lunar.py +cat /home/gilles/Documents/vscode/jardin/backend/app/services/lunar.py | head -80 +``` + +**Step 2: VĂ©rifier le modĂšle LunarCalendarEntry** + +```bash +cat /home/gilles/Documents/vscode/jardin/backend/app/models/settings.py +``` + +**Step 3: Modifier le router lunar pour persister les donnĂ©es** + +Dans `backend/app/routers/lunar.py`, aprĂšs avoir calculĂ© les donnĂ©es du mois, sauvegarder chaque jour dans `lunarcalendarentry`. Pattern upsert : + +```python +from app.models.settings import LunarCalendarEntry +from sqlmodel import Session, select +from app.database import get_session + +# Dans l'endpoint GET /api/lunar?month=YYYY-MM : +# AprĂšs avoir calculĂ© days_data, persister : +for day_data in days_data: + existing = session.exec( + select(LunarCalendarEntry).where(LunarCalendarEntry.date == day_data['date']) + ).first() + if not existing: + entry = LunarCalendarEntry( + date=day_data['date'], + phase=day_data.get('phase'), + illumination=day_data.get('illumination'), + type_jour=day_data.get('type_jour'), + # ... autres champs selon le modĂšle + ) + session.add(entry) +session.commit() +``` + +**Step 4: PrĂ©-populer les 3 prochains mois au dĂ©marrage** + +Dans `backend/app/main.py`, dans le lifespan, aprĂšs la migration, appeler l'endpoint lunar pour les 3 mois autour de la date courante pour prĂ©-remplir la table. + +**Step 5: VĂ©rifier** + +```bash +sqlite3 /home/gilles/Documents/vscode/jardin/data/jardin.db "SELECT count(*) FROM lunarcalendarentry;" +# Doit ĂȘtre > 0 aprĂšs restart +``` + +**Step 6: Commit** + +```bash +git commit -m "feat(lunar): persister les donnĂ©es calculĂ©es dans lunarcalendarentry" +``` + +--- + +## Task 6: Grille jardin — SĂ©lection de case pour ajouter une plantation + +**PrioritĂ©:** Haute (demandĂ© explicitement : "je peux sĂ©lectionner des cases de la grille jardin pour ajouter une plantation") + +**Files:** +- Modify: `frontend/src/views/JardinDetailView.vue` +- Read first: `frontend/src/api/plants.ts` et `frontend/src/api/gardens.ts` pour les types + +**Ce que ça fait:** +- Clic sur une case libre → modal s'ouvre avec formulaire de crĂ©ation de plantation +- Le formulaire propose: plante (liste dĂ©roulante), date plantation, notes +- POST vers /api/plantings avec `garden_id`, `plant_id`, `cell_row`, `cell_col` +- AprĂšs succĂšs: la case passe en "occupĂ©" (orange) + +**Step 1: Lire les fichiers API nĂ©cessaires** + +```bash +cat /home/gilles/Documents/vscode/jardin/frontend/src/api/plants.ts | head -40 +cat /home/gilles/Documents/vscode/jardin/frontend/src/api/gardens.ts | head -40 +``` + +**Step 2: Ajouter le modal de plantation dans JardinDetailView.vue** + +Ajouter dans le `