refactor: #85 T2 extrait le CRUD webhooks vers backend/routers (comportement inchange)
This commit is contained in:
+9
-1
@@ -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).
|
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
|
> **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
|
## [2.27.2] — 2026-09-26
|
||||||
|
|
||||||
### Modifié
|
### 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`.**
|
- **#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
|
`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
|
nouveau `backend/routers/health.py` (monté dans `main.py`) et le modèle
|
||||||
|
|||||||
+3
-3
@@ -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.
|
**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.
|
||||||
|
|
||||||
[]()
|
[]()
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
[](https://www.python.org/)
|
[](https://www.python.org/)
|
||||||
@@ -976,8 +976,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
|
|||||||
|
|
||||||
## 📝 Changelog
|
## 📝 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*
|
||||||
|
|||||||
@@ -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.
|
**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.
|
||||||
|
|
||||||
[]()
|
[]()
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
[](https://www.python.org/)
|
[](https://www.python.org/)
|
||||||
@@ -1151,8 +1151,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
|
|||||||
|
|
||||||
## 📝 Changelog
|
## 📝 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*
|
||||||
|
|||||||
+4
-39
@@ -93,7 +93,6 @@ from backend.schemas import (
|
|||||||
VaultSettingsResponse,
|
VaultSettingsResponse,
|
||||||
VaultsStatusResponse,
|
VaultsStatusResponse,
|
||||||
VaultStatsResponse,
|
VaultStatsResponse,
|
||||||
WebhookModel,
|
|
||||||
)
|
)
|
||||||
from backend.search import (
|
from backend.search import (
|
||||||
init_inverted_index,
|
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.bookslm_routes import router as bookslm_router
|
||||||
from backend.export import ExportError, export_epub, export_html, export_md_bundle
|
from backend.export import ExportError, export_epub, export_html, export_md_bundle
|
||||||
from backend.routers.health import router as health_router
|
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.saved_searches import delete_saved, get_saved, save_search
|
||||||
from backend.share import (
|
from backend.share import (
|
||||||
create_share,
|
create_share,
|
||||||
@@ -957,19 +957,14 @@ from backend.share import (
|
|||||||
update_shares_after_rename,
|
update_shares_after_rename,
|
||||||
)
|
)
|
||||||
from backend.skills_routes import router as skills_router
|
from backend.skills_routes import router as skills_router
|
||||||
from backend.webhooks import (
|
from backend.webhooks import dispatch_webhooks
|
||||||
create_webhook,
|
|
||||||
delete_webhook,
|
|
||||||
dispatch_webhooks,
|
|
||||||
get_webhooks,
|
|
||||||
update_webhook,
|
|
||||||
)
|
|
||||||
|
|
||||||
app.include_router(auth_router)
|
app.include_router(auth_router)
|
||||||
app.include_router(ai_router)
|
app.include_router(ai_router)
|
||||||
app.include_router(bookslm_router)
|
app.include_router(bookslm_router)
|
||||||
app.include_router(skills_router)
|
app.include_router(skills_router)
|
||||||
app.include_router(health_router) # ROADMAP #85 T1 — System / health
|
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)
|
# Admin Dashboard endpoints (system stats, audit logs, backups, stream)
|
||||||
try:
|
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
|
# Share (public document) endpoints
|
||||||
|
|||||||
@@ -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"}
|
||||||
Generated
+1
-1
@@ -2626,7 +2626,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "obsigate-desktop"
|
name = "obsigate-desktop"
|
||||||
version = "2.27.2"
|
version = "2.27.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "obsigate-desktop"
|
name = "obsigate-desktop"
|
||||||
version = "2.27.2"
|
version = "2.27.3"
|
||||||
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
|
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
|
||||||
authors = ["Bruno Charest"]
|
authors = ["Bruno Charest"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
|
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
|
||||||
"productName": "ObsiGate",
|
"productName": "ObsiGate",
|
||||||
"version": "2.27.2",
|
"version": "2.27.3",
|
||||||
"identifier": "com.obsigate.desktop",
|
"identifier": "com.obsigate.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../frontend",
|
"frontendDist": "../frontend",
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
# ObsiGate — Roadmap
|
# 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
|
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
||||||
> vers les fonctionnalités livrées.
|
> vers les fonctionnalités livrées.
|
||||||
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
> - **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
|
- **Effort :** 8-12 jours | **Impact :** 🟡 | **Zone :** backend
|
||||||
- **Décision 2026-09-26 : prioritaire (axe Dette & sécurité).**
|
- **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.
|
- **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 :**
|
- **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
|
- [ ] 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)
|
- [ ] 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)
|
- [ ] 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)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsigate",
|
"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.",
|
"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",
|
"main": "patch.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|||||||
Reference in New Issue
Block a user