From 3eb02561276e0d7b087c75367d5760e0bd111502 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Sat, 26 Sep 2026 12:43:38 -0400 Subject: [PATCH] refactor: #85 T2 extrait le CRUD webhooks vers backend/routers (comportement inchange) --- CHANGELOG.md | 10 ++++++- README.fr.md | 6 ++--- README.md | 6 ++--- VERSION | 2 +- backend/main.py | 43 +++-------------------------- backend/routers/webhooks.py | 54 +++++++++++++++++++++++++++++++++++++ desktop/Cargo.lock | 2 +- desktop/Cargo.toml | 2 +- desktop/tauri.conf.json | 2 +- docs/ROADMAP.md | 6 ++--- package.json | 2 +- 11 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 backend/routers/webhooks.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f7360c..5d279a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). > **En cours de développement** : les changements à venir sont listés dans la section -> [Unreleased](#unreleased). La dernière version livrée est **2.27.2**. +> [Unreleased](#unreleased). La dernière version livrée est **2.27.3**. --- @@ -14,10 +14,18 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.27.3] — 2026-09-26 + +--- + ## [2.27.2] — 2026-09-26 ### Modifié +- **#85 (T2) — extraction du domaine `webhooks` hors du monolithe `backend/main.py`.** + Le CRUD `GET/POST/PATCH/DELETE /api/webhooks` (admin) est servi par le + nouveau `backend/routers/webhooks.py` — chemins, réponses, tags OpenAPI et + authentification inchangés (aucun impact utilisateur). - **#85 (T1) — extraction du domaine `health` hors du monolithe `backend/main.py`.** `GET /api/health` et `GET /api/health/detailed` (admin) sont servis par le nouveau `backend/routers/health.py` (monté dans `main.py`) et le modèle diff --git a/README.fr.md b/README.fr.md index 635cf5f..b7a17fe 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,7 +4,7 @@ **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. -[![Version](https://img.shields.io/badge/Version-2.27.2-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.27.3-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -976,8 +976,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l ## 📝 Changelog -Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.27.2). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.27.3). --- -*Projet : ObsiGate | Version : 2.27.2 | Dernière mise à jour : Septembre 2026* +*Projet : ObsiGate | Version : 2.27.3 | Dernière mise à jour : Septembre 2026* diff --git a/README.md b/README.md index f7b2521..2584bef 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. -[![Version](https://img.shields.io/badge/Version-2.27.2-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.27.3-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -1151,8 +1151,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ## 📝 Changelog -See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.27.2). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.27.3). --- -*Project: ObsiGate | Version: 2.27.2 | Last updated: September 2026* +*Project: ObsiGate | Version: 2.27.3 | Last updated: September 2026* diff --git a/VERSION b/VERSION index 05abc55..2adce90 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.27.2 +2.27.3 diff --git a/backend/main.py b/backend/main.py index b063989..2d47192 100644 --- a/backend/main.py +++ b/backend/main.py @@ -93,7 +93,6 @@ from backend.schemas import ( VaultSettingsResponse, VaultsStatusResponse, VaultStatsResponse, - WebhookModel, ) from backend.search import ( init_inverted_index, @@ -947,6 +946,7 @@ from backend.ai_routes import router as ai_router from backend.bookslm_routes import router as bookslm_router from backend.export import ExportError, export_epub, export_html, export_md_bundle from backend.routers.health import router as health_router +from backend.routers.webhooks import router as webhooks_router from backend.saved_searches import delete_saved, get_saved, save_search from backend.share import ( create_share, @@ -957,19 +957,14 @@ from backend.share import ( update_shares_after_rename, ) from backend.skills_routes import router as skills_router -from backend.webhooks import ( - create_webhook, - delete_webhook, - dispatch_webhooks, - get_webhooks, - update_webhook, -) +from backend.webhooks import dispatch_webhooks app.include_router(auth_router) app.include_router(ai_router) app.include_router(bookslm_router) app.include_router(skills_router) app.include_router(health_router) # ROADMAP #85 T1 — System / health +app.include_router(webhooks_router) # ROADMAP #85 T2 — Webhooks # Admin Dashboard endpoints (system stats, audit logs, backups, stream) try: @@ -4256,39 +4251,9 @@ async def api_dashboard(current_user=Depends(require_auth)): # --------------------------------------------------------------------------- -# Webhook CRUD endpoints +# Webhook CRUD endpoints : voir backend.routers.webhooks (#85 T2) # --------------------------------------------------------------------------- -@app.get("/api/webhooks", response_model=list[WebhookModel]) -async def api_webhooks_list(current_user=Depends(require_admin)): - return get_webhooks() - - -@app.post("/api/webhooks", response_model=WebhookModel) -async def api_webhooks_create(body: dict = Body(...), current_user=Depends(require_admin)): - name = body.get("name", "Unnamed") - url = body.get("url", "") - events = body.get("events", []) - secret = body.get("secret") - if not url: - raise HTTPException(400, "URL is required") - return create_webhook(name, url, events, secret) - - -@app.patch("/api/webhooks/{webhook_id}", response_model=WebhookModel) -async def api_webhooks_update(webhook_id: str, body: dict = Body(...), current_user=Depends(require_admin)): - result = update_webhook(webhook_id, body) - if not result: - raise HTTPException(404, "Webhook not found") - return result - - -@app.delete("/api/webhooks/{webhook_id}", response_model=StatusResponse) -async def api_webhooks_delete(webhook_id: str, current_user=Depends(require_admin)): - if not delete_webhook(webhook_id): - raise HTTPException(404, "Webhook not found") - return {"status": "deleted"} - # --------------------------------------------------------------------------- # Share (public document) endpoints diff --git a/backend/routers/webhooks.py b/backend/routers/webhooks.py new file mode 100644 index 0000000..8cec7ec --- /dev/null +++ b/backend/routers/webhooks.py @@ -0,0 +1,54 @@ +"""Webhook CRUD endpoints (ROADMAP #85, tranche 2). + +Handlers déplacés depuis :mod:`backend.main` sans changement de +comportement : mêmes chemins (``/api/webhooks``), même modèle de réponse +(:class:`backend.schemas.WebhookModel`), même dépendance admin. La logique +métier vit déjà dans :mod:`backend.webhooks` (validation d'URL anti-SSRF, +store ``webhook_secrets.json`` — BUG-026). +""" + +from fastapi import APIRouter, Body, Depends, HTTPException + +from backend.auth.middleware import require_admin +from backend.schemas import StatusResponse, WebhookModel +from backend.webhooks import ( + create_webhook, + delete_webhook, + get_webhooks, + update_webhook, +) + +router = APIRouter(prefix="/api/webhooks", tags=["webhooks"]) + + +@router.get("", response_model=list[WebhookModel]) +async def api_webhooks_list(current_user=Depends(require_admin)): + return get_webhooks() + + +@router.post("", response_model=WebhookModel) +async def api_webhooks_create(body: dict = Body(...), current_user=Depends(require_admin)): + name = body.get("name", "Unnamed") + url = body.get("url", "") + events = body.get("events", []) + secret = body.get("secret") + if not url: + raise HTTPException(400, "URL is required") + return create_webhook(name, url, events, secret) + + +@router.patch("/{webhook_id}", response_model=WebhookModel) +async def api_webhooks_update( + webhook_id: str, body: dict = Body(...), current_user=Depends(require_admin) +): + result = update_webhook(webhook_id, body) + if not result: + raise HTTPException(404, "Webhook not found") + return result + + +@router.delete("/{webhook_id}", response_model=StatusResponse) +async def api_webhooks_delete(webhook_id: str, current_user=Depends(require_admin)): + if not delete_webhook(webhook_id): + raise HTTPException(404, "Webhook not found") + return {"status": "deleted"} diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index f373c73..17a33d9 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.27.2" +version = "2.27.3" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 244891c..5838004 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.27.2" +version = "2.27.3" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" authors = ["Bruno Charest"] edition = "2021" diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json index d61ffa1..cfaa01f 100644 --- a/desktop/tauri.conf.json +++ b/desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "productName": "ObsiGate", - "version": "2.27.2", + "version": "2.27.3", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 1221319..8cb12a4 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ObsiGate — Roadmap -> **Version :** 2.27.2 | **Dernière mise à jour :** 2026-09-26 +> **Version :** 2.27.3 | **Dernière mise à jour :** 2026-09-26 > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > vers les fonctionnalités livrées. > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** @@ -67,10 +67,10 @@ - **Effort :** 8-12 jours | **Impact :** 🟡 | **Zone :** backend - **Décision 2026-09-26 : prioritaire (axe Dette & sécurité).** -- **Statut :** 🔵 en cours depuis 2026-09-26 — découpe par tranches à impact minimal (comportement inchangé, un domaine par commit). **T1 livrée (v2.27.2) :** `health` (`/api/health`, `/api/health/detailed` → `backend/routers/health.py`, `HealthResponse` → `schemas.py`). +- **Statut :** 🔵 en cours depuis 2026-09-26 — découpe par tranches à impact minimal (comportement inchangé, un domaine par commit). **T1 livrée (v2.27.2) :** `health` (`/api/health`, `/api/health/detailed` → `backend/routers/health.py`, `HealthResponse` → `schemas.py`). **T2 livrée (v2.27.3) :** `webhooks` (CRUD `/api/webhooks` → `backend/routers/webhooks.py`, logique déjà dans `backend/webhooks.py`). - **Description :** extraire le monolithe `backend/main.py` (~4 827 lignes au 2026-09-26, ~17 % du backend) en routers FastAPI par domaine et rendre persistant l'état qui ne l'est pas (index de recherche, JTI révoqués, compteurs de rate-limit) pour préparer le multi-nœuds. L'état mémoire actuel (index, inverted index, vecteurs sémantiques, `SSEManager`, collab) rend le multi-workers unsafe. - **Sous-tâches :** - - [ ] Routers par domaine : files, search, share, webhooks, plugins, collab, admin, ai — `main.py` conservé comme assemblage (< 500 lignes) ; dédupliquer les modèles Pydantic vers `schemas.py`. **Avancement :** `health` ✅ (T1, `backend/routers/health.py`) ; `tools/registry.py` existe déjà (permissions/quotas/redaction — à compléter, pas à créer) + - [ ] Routers par domaine : files, search, share, webhooks, plugins, collab, admin, ai — `main.py` conservé comme assemblage (< 500 lignes) ; dédupliquer les modèles Pydantic vers `schemas.py`. **Avancement :** `health` ✅ (T1, `backend/routers/health.py`), `webhooks` ✅ (T2, `backend/routers/webhooks.py`) ; `tools/registry.py` existe déjà (permissions/quotas/redaction — à compléter, pas à créer) - [ ] Compléter `tools/registry.py` (existant : permissions/quotas/redaction) comme contrat central des outils IA si des manques sont constatés - [ ] Persister index, JTI révoqués et compteurs de rate-limit (SQLite par défaut, Redis en option multi-nœuds ; le rate-limit actuel est in-memory mono-process) - [ ] Verrous asyncio autour de l'index global et des stores JSON ; auditer les `except Exception` larges (> 100 occurrences) : best-effort (backup/audit) vs masquage d'erreur (erreurs typées 4xx/5xx + test) diff --git a/package.json b/package.json index 873eed1..9483e0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsigate", - "version": "2.27.2", + "version": "2.27.3", "description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.", "main": "patch.js", "directories": {