571 lines
20 KiB
Markdown
571 lines
20 KiB
Markdown
# ObsiGate
|
||
|
||
**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://www.docker.com/)
|
||
[](https://www.python.org/)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ [🔍 Recherche...] [☀/🌙 Thème] ObsiGate │
|
||
├──────────────┬──────────────────────────────────────────┤
|
||
│ SIDEBAR │ CONTENT AREA │
|
||
│ ▼ Recettes │ 📄 Titre du fichier │
|
||
│ 📁 Soupes │ Tags: #recette #rapide │
|
||
│ 📄 Pizza │ [Contenu Markdown rendu] │
|
||
│ ▼ IT │ │
|
||
│ 📁 Docker │ │
|
||
│ Tags Cloud │ │
|
||
└──────────────┴──────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 Table des matières
|
||
|
||
- [Fonctionnalités](#-fonctionnalités)
|
||
- [Architecture](#-architecture)
|
||
- [Prérequis](#-prérequis)
|
||
- [Installation rapide](#-installation-rapide)
|
||
- [Configuration détaillée](#-configuration-détaillée)
|
||
- [Variables d'environnement](#-variables-denvironnement)
|
||
- [Ajouter une nouvelle vault](#-ajouter-une-nouvelle-vault)
|
||
- [Build multi-platform](#-build-multi-platform)
|
||
- [Utilisation](#-utilisation)
|
||
- [API](#-api)
|
||
- [Performance](#-performance)
|
||
- [Dépannage](#-dépannage)
|
||
- [Stack technique](#-stack-technique)
|
||
- [Changelog](#-changelog)
|
||
|
||
---
|
||
|
||
## ✨ Fonctionnalités
|
||
|
||
- **🗂️ Multi-vault** : Visualisez plusieurs vaults Obsidian simultanément
|
||
- **🌳 Navigation arborescente** : Parcourez vos dossiers et fichiers dans la sidebar
|
||
- **🔍 Recherche fulltext** : Recherche instantanée dans le contenu et les titres
|
||
- **🏷️ Tag cloud** : Filtrage par tags extraits des frontmatters YAML
|
||
- **🔗 Wikilinks** : Les `[[liens internes]]` Obsidian sont cliquables
|
||
- **🖼️ Images Obsidian** : Support complet des syntaxes d'images Obsidian avec résolution intelligente
|
||
- **🎨 Syntax highlight** : Coloration syntaxique des blocs de code
|
||
- **🌓 Thème clair/sombre** : Toggle persisté en localStorage
|
||
- **🐳 Docker multi-platform** : linux/amd64, linux/arm64, linux/arm/v7, linux/386
|
||
- **🔒 Sécurité** : Protection contre le path traversal, utilisateur non-root dans Docker
|
||
- **❤️ Healthcheck** : Endpoint `/api/health` intégré pour Docker et monitoring
|
||
|
||
---
|
||
|
||
## 🚀 Prérequis
|
||
|
||
### Système requis
|
||
- **Docker** >= 20.10
|
||
- **docker-compose** >= 2.0
|
||
- **Espace disque** : ~200MB pour l'image Docker
|
||
|
||
### Systèmes supportés
|
||
- Linux (Ubuntu, Debian, CentOS, etc.)
|
||
- macOS (Intel et Apple Silicon)
|
||
- Windows (avec Docker Desktop)
|
||
- NAS compatibles Docker (Synology, QNAP, etc.)
|
||
|
||
---
|
||
|
||
## ⚡ Installation rapide
|
||
|
||
### 1. Cloner le dépôt
|
||
|
||
```bash
|
||
git clone https://git.dracodev.net/Projets/ObsiGate.git
|
||
cd ObsiGate
|
||
```
|
||
|
||
### 2. Configurer vos vaults
|
||
|
||
Éditez le fichier `docker-compose.yml` pour ajouter vos vaults Obsidian :
|
||
|
||
```yaml
|
||
volumes:
|
||
- /chemin/absolu/vers/votre/vault:/vaults/NomDeVotreVault:ro
|
||
```
|
||
|
||
> **Important** : Le chemin doit être absolu et le volume en lecture seule (`:ro`)
|
||
|
||
### 3. Lancer l'application
|
||
|
||
```bash
|
||
# Build local de l'image + démarrage
|
||
docker-compose up -d --build
|
||
|
||
# Vérifier les logs
|
||
docker-compose logs -f obsigate
|
||
```
|
||
|
||
> **Note** : ObsiGate est construit localement depuis le `Dockerfile` du projet. Sans build local, Docker essaiera de télécharger une image distante `obsigate:latest` qui n'existe pas forcément.
|
||
|
||
### 4. Accéder à l'interface
|
||
|
||
Ouvrez votre navigateur sur : **http://localhost:2020**
|
||
|
||
---
|
||
|
||
## ⚙️ Configuration détaillée
|
||
|
||
### Étape 1 : Préparation des vaults
|
||
|
||
1. **Localisez vos vaults Obsidian** sur votre système
|
||
2. **Notez les chemins absolus** vers chaque dossier `.obsidian`
|
||
3. **Vérifiez les permissions** : Docker doit pouvoir lire ces dossiers
|
||
|
||
### Étape 2 : Configuration docker-compose.yml
|
||
|
||
Voici un exemple complet :
|
||
|
||
```yaml
|
||
services:
|
||
obsigate:
|
||
build:
|
||
context: .
|
||
image: obsigate:latest
|
||
container_name: obsigate
|
||
restart: unless-stopped
|
||
ports:
|
||
- "2020:8080" # Port local 2020 → Port conteneur 8080
|
||
volumes:
|
||
# Exemples de vaults (adaptez à vos chemins)
|
||
- /home/user/Documents/Obsidian-Recettes:/vaults/Recettes:ro
|
||
- /home/user/Documents/Obsidian-IT:/vaults/IT:ro
|
||
- /home/user/Documents/Obsidian-Perso:/vaults/Perso:ro
|
||
environment:
|
||
# Configuration des vaults
|
||
- VAULT_1_NAME=Recettes
|
||
- VAULT_1_PATH=/vaults/Recettes
|
||
- VAULT_2_NAME=IT
|
||
- VAULT_2_PATH=/vaults/IT
|
||
- VAULT_3_NAME=Perso
|
||
- VAULT_3_PATH=/vaults/Perso
|
||
```
|
||
|
||
### Étape 3 : Construction de l'image
|
||
|
||
```bash
|
||
# Build l'image Docker
|
||
docker build -t obsigate:latest .
|
||
|
||
# Puis démarrer le service
|
||
docker-compose up -d --build
|
||
|
||
# Ou utilisez le script de build multi-platform
|
||
chmod +x build.sh
|
||
./build.sh
|
||
```
|
||
|
||
> **Compatibilité Docker** : l'image utilise la variante minimale de `uvicorn` et une version de `fastapi` compatible (`0.110.3`) afin d'éviter certaines dépendances optionnelles natives (`watchfiles`, `uvloop`, `httptools`, `fastapi-cli`, etc.) qui peuvent échouer au build sur certaines plateformes comme Alpine, ARM ou i386.
|
||
|
||
---
|
||
|
||
## 🌍 Variables d'environnement
|
||
|
||
Les vaults sont configurées par paires de variables `VAULT_N_NAME` / `VAULT_N_PATH` (N = 1, 2, 3…) :
|
||
|
||
| Variable | Description | Exemple |
|
||
|----------|-------------|---------|
|
||
| `VAULT_1_NAME` | Nom affiché de la vault | `Recettes` |
|
||
| `VAULT_1_PATH` | Chemin dans le conteneur | `/vaults/Obsidian-RECETTES` |
|
||
| `VAULT_1_ATTACHMENTS_PATH` | Chemin relatif vers le dossier d'attachements (optionnel) | `06_Boite_a_Outils/6.2_Attachments` |
|
||
| `VAULT_1_SCAN_ATTACHMENTS` | Activer le scan d'images au démarrage (optionnel, défaut: true) | `true` |
|
||
| `VAULT_2_NAME` | Nom affiché de la vault | `IT` |
|
||
| `VAULT_2_PATH` | Chemin dans le conteneur | `/vaults/Obsidian_IT` |
|
||
|
||
**Règles de nommage :**
|
||
- Utilisez uniquement des lettres, chiffres et tirets
|
||
- Pas d'espaces ou caractères spéciaux
|
||
- Le nom doit correspondre au chemin dans le conteneur
|
||
|
||
---
|
||
|
||
## ➕ Ajouter une nouvelle vault
|
||
|
||
### Méthode 1 : Édition directe
|
||
|
||
1. **Arrêtez le conteneur** :
|
||
```bash
|
||
docker-compose down
|
||
```
|
||
|
||
2. **Ajoutez un volume** dans `docker-compose.yml` :
|
||
```yaml
|
||
volumes:
|
||
- /nouveau/chemin/vault:/vaults/NouvelleVault:ro
|
||
```
|
||
|
||
3. **Ajoutez les variables d'environnement** :
|
||
```yaml
|
||
environment:
|
||
- VAULT_4_NAME=NouvelleVault
|
||
- VAULT_4_PATH=/vaults/NouvelleVault
|
||
```
|
||
|
||
4. **Redémarrez** :
|
||
```bash
|
||
docker-compose up -d --build
|
||
```
|
||
|
||
### Méthode 2 : Hot-reload (recommandé)
|
||
|
||
1. **Ajoutez le volume et les variables** comme ci-dessus
|
||
2. **Appliquez les changements** :
|
||
```bash
|
||
docker-compose up -d --build
|
||
```
|
||
3. **Rechargez l'index** via l'interface ou l'API :
|
||
```bash
|
||
curl http://localhost:2020/api/index/reload
|
||
```
|
||
|
||
---
|
||
|
||
## 🔨 Build multi-platform
|
||
|
||
Pour les architectures multiples (Raspberry Pi, NAS, etc.) :
|
||
|
||
```bash
|
||
# Rendre le script exécutable
|
||
chmod +x build.sh
|
||
|
||
# Lancer le build multi-platform
|
||
./build.sh
|
||
```
|
||
|
||
**Architectures supportées :**
|
||
- `linux/amd64` (PC, serveurs standards)
|
||
- `linux/arm64` (Raspberry Pi 4, Apple Silicon, NAS modernes)
|
||
- `linux/arm/v7` (Raspberry Pi 3, anciens NAS)
|
||
- `linux/386` (Systèmes 32-bit legacy)
|
||
|
||
**Pour un build local uniquement :**
|
||
```bash
|
||
docker buildx build --platform linux/amd64 --load -t obsigate:latest .
|
||
```
|
||
|
||
---
|
||
|
||
## <20>️ Rendu d'images Obsidian
|
||
|
||
ObsiGate supporte **toutes les syntaxes d'images Obsidian** avec un système de résolution intelligent multi-stratégies.
|
||
|
||
### Syntaxes supportées
|
||
|
||
1. **Standard Markdown avec attributs HTML** (compatible Obsidian) :
|
||
```markdown
|
||
[<img width="180" height="60" src="path/to/image.svg"/>](https://example.com)
|
||
```
|
||
|
||
2. **Wiki-link embed avec chemin complet** :
|
||
```markdown
|
||
![[06_Boite_a_Outils/6.2_Attachments/image.svg]]
|
||
```
|
||
|
||
3. **Wiki-link embed avec nom de fichier uniquement** :
|
||
```markdown
|
||
![[image.svg]]
|
||
```
|
||
|
||
4. **Markdown standard** :
|
||
```markdown
|
||

|
||
```
|
||
|
||
### Résolution intelligente des chemins
|
||
|
||
ObsiGate utilise 7 stratégies de résolution par ordre de priorité :
|
||
|
||
1. **Chemin absolu** : Si le chemin est absolu et existe
|
||
2. **Dossier d'attachements configuré** : Via `VAULT_N_ATTACHMENTS_PATH`
|
||
3. **Index de démarrage (match unique)** : Recherche par nom de fichier dans l'index
|
||
4. **Même répertoire** : Relatif au fichier markdown courant
|
||
5. **Racine du vault** : Relatif à la racine du vault
|
||
6. **Index de démarrage (match le plus proche)** : Si plusieurs fichiers portent le même nom
|
||
7. **Fallback** : Affiche un placeholder stylisé `[image not found: filename.ext]`
|
||
|
||
### Configuration
|
||
|
||
Pour optimiser la résolution, configurez le dossier d'attachements de chaque vault :
|
||
|
||
```yaml
|
||
environment:
|
||
- VAULT_1_NAME=MonVault
|
||
- VAULT_1_PATH=/vaults/MonVault
|
||
- VAULT_1_ATTACHMENTS_PATH=Assets/Images # Chemin relatif
|
||
- VAULT_1_SCAN_ATTACHMENTS=true # Activer le scan (défaut)
|
||
```
|
||
|
||
### Rescan manuel
|
||
|
||
Pour rescanner les images d'un vault après ajout/suppression :
|
||
|
||
```bash
|
||
curl -X POST http://localhost:2020/api/attachments/rescan/MonVault
|
||
```
|
||
|
||
---
|
||
|
||
## <20>📖 Utilisation
|
||
|
||
### Interface web
|
||
|
||
1. **Navigation** : Cliquez sur les vaults dans la sidebar pour les développer
|
||
2. **Recherche** : Utilisez la barre de recherche pour chercher dans toutes les vaults
|
||
3. **Tags** : Cliquez sur les tags pour filtrer les contenus
|
||
4. **Wikilinks** : Les liens `[[page]]` sont cliquables et navigables
|
||
5. **Images** : Toutes les syntaxes d'images Obsidian sont rendues automatiquement
|
||
6. **Thème** : Basculez entre thème clair/sombre avec l'icône 🌙/☀️
|
||
|
||
### Raccourcis clavier
|
||
|
||
| Action | Raccourci |
|
||
|--------|-----------|
|
||
| Recherche | `Ctrl + K` ou `/` |
|
||
| Toggle thème | `Ctrl + T` |
|
||
| Focus recherche | `Esc` |
|
||
|
||
---
|
||
|
||
## 🔌 API
|
||
|
||
ObsiGate expose une API REST complète :
|
||
|
||
| Endpoint | Description | Méthode |
|
||
|----------|-------------|---------|
|
||
| `/api/health` | Health check (status, version, stats) | GET |
|
||
| `/api/vaults` | Liste des vaults configurées | GET |
|
||
| `/api/browse/{vault}?path=` | Navigation dans les dossiers | GET |
|
||
| `/api/file/{vault}?path=` | Contenu rendu d'un fichier | GET |
|
||
| `/api/file/{vault}/raw?path=` | Contenu brut d'un fichier | GET |
|
||
| `/api/file/{vault}/download?path=` | Téléchargement d'un fichier | GET |
|
||
| `/api/file/{vault}/save?path=` | Sauvegarder un fichier | PUT |
|
||
| `/api/file/{vault}?path=` | Supprimer un fichier | DELETE |
|
||
| `/api/search?q=&vault=&tag=` | Recherche fulltext | GET |
|
||
| `/api/tags?vault=` | Tags uniques avec compteurs | GET |
|
||
| `/api/index/reload` | Force un re-scan des vaults | GET |
|
||
| `/api/image/{vault}?path=` | Servir une image avec MIME type approprié | GET |
|
||
| `/api/attachments/rescan/{vault}` | Rescanner les images d'un vault | POST |
|
||
| `/api/attachments/stats?vault=` | Statistiques d'images indexées | GET |
|
||
|
||
> Tous les endpoints exposent des schémas Pydantic documentés. La doc interactive est disponible sur `/docs` (Swagger UI).
|
||
|
||
**Exemple d'utilisation :**
|
||
```bash
|
||
# Health check
|
||
curl http://localhost:2020/api/health
|
||
|
||
# Lister les vaults
|
||
curl http://localhost:2020/api/vaults
|
||
|
||
# Rechercher
|
||
curl "http://localhost:2020/api/search?q=recette&vault=all"
|
||
|
||
# Obtenir un fichier
|
||
curl "http://localhost:2020/api/file/Recettes?path=pizza.md"
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 Dépannage
|
||
|
||
### Problèmes courants
|
||
|
||
**Port déjà utilisé :**
|
||
```bash
|
||
# Vérifier qui utilise le port
|
||
sudo netstat -tulpn | grep 2020
|
||
# Changer le port dans docker-compose.yml
|
||
ports:
|
||
- "2021:8080"
|
||
```
|
||
|
||
**Vault non trouvée :**
|
||
- Vérifiez que les chemins sont absolus
|
||
- Assurez-vous que les permissions permettent la lecture
|
||
- Redémarrez le conteneur après modification
|
||
|
||
**Build échoue :**
|
||
```bash
|
||
# Nettoyer et rebuild
|
||
docker system prune -f
|
||
docker-compose down
|
||
docker build --no-cache -t obsigate:latest .
|
||
docker-compose up -d
|
||
```
|
||
|
||
**Logs pour debugging :**
|
||
```bash
|
||
# Logs en temps réel
|
||
docker-compose logs -f obsigate
|
||
|
||
# Logs détaillés
|
||
docker-compose logs --tail=100 obsigate
|
||
```
|
||
|
||
---
|
||
|
||
## ⚡ Performance
|
||
|
||
| Métrique | Estimation |
|
||
|----------|------------|
|
||
| **Indexation** | ~1–2s pour 1 000 fichiers markdown |
|
||
| **Recherche fulltext** | < 50ms (index en mémoire, zéro I/O disque) |
|
||
| **Résolution wikilinks** | O(1) via table de lookup |
|
||
| **Mémoire** | ~80–150MB par 1 000 fichiers (contenu capé à 100 KB/fichier) |
|
||
| **Image Docker** | ~180MB (multi-stage, sans outils de build) |
|
||
| **CPU** | Minimal ; pas de polling, pas de watchers |
|
||
|
||
### Optimisations clés (v1.1.0)
|
||
|
||
- **Recherche sans I/O** : le contenu des fichiers est mis en cache dans l'index mémoire
|
||
- **Scoring multi-facteurs** : titre exact (+20), titre partiel (+10), chemin (+5), tag (+3), fréquence contenu (x1 par occurrence, capé à 10)
|
||
- **Rendu Markdown singleton** : le renderer mistune est instancié une seule fois
|
||
- **AbortController** : les requêtes de recherche obsolètes sont annulées côté client
|
||
- **Debounced icon rendering** : `lucide.createIcons()` est batché via `requestAnimationFrame`
|
||
|
||
---
|
||
|
||
## 🛡️ Sécurité
|
||
|
||
- **Path traversal** : tous les endpoints fichier valident que le chemin résolu reste dans la vault
|
||
- **Utilisateur non-root** : le conteneur Docker tourne sous l'utilisateur `obsigate`
|
||
- **Volumes read-only** : les vaults sont montées en `:ro` par défaut dans docker-compose
|
||
|
||
---
|
||
|
||
## 🏗️ Stack technique
|
||
|
||
- **Backend** : Python 3.11 + FastAPI 0.110 + Uvicorn
|
||
- **Frontend** : Vanilla JS + HTML + CSS (zéro framework, zéro build)
|
||
- **Rendu Markdown** : mistune 3.x
|
||
- **Image Docker** : python:3.11-slim (multi-stage)
|
||
- **Base de données** : Aucune (index en mémoire uniquement)
|
||
- **Architecture** : SPA + API REST
|
||
|
||
---
|
||
|
||
## 🏠 Architecture
|
||
|
||
```
|
||
┌─────────────────┐ ┌─────────────────────────────────────────┐
|
||
│ Navigateur │◄───►│ FastAPI (backend/main.py) │
|
||
│ (SPA) │ REST │ │
|
||
│ │ │ ┌──────────────┐ ┌──────────────┐ │
|
||
│ app.js │ │ │ indexer.py │ │ search.py │ │
|
||
│ style.css │ │ │ (scan+cache)│ │ (in-memory) │ │
|
||
│ index.html │ │ └───────┬──────┘ └──────┬───────┘ │
|
||
└─────────────────┘ │ │ │ │
|
||
│ └──────┬───────┘ │
|
||
│ │ │
|
||
│ ┌────────┴─────────┐ │
|
||
│ │ Index en mémoire │ │
|
||
│ │ (fichiers, tags, │ │
|
||
│ │ contenu, lookup)│ │
|
||
│ └──────────────────┘ │
|
||
└─────────────────────────────────────────┘
|
||
┌───────────────────────────────┐
|
||
│ Filesystem (vaults montées) │
|
||
│ /vaults/Recettes (ro) │
|
||
│ /vaults/IT (ro) │
|
||
└───────────────────────────────┘
|
||
```
|
||
|
||
**Flux de données :**
|
||
1. Au démarrage, `indexer.py` scanne tous les vaults en parallèle (thread pool)
|
||
2. Le contenu, les tags (YAML + inline) et les métadonnées sont mis en cache en mémoire
|
||
3. Une table de lookup O(1) est construite pour la résolution des wikilinks
|
||
4. Les requêtes de recherche utilisent l'index en mémoire (zéro I/O disque)
|
||
5. Le frontend SPA communique via REST et gère l'état côté client
|
||
|
||
---
|
||
|
||
## 📝 Développement
|
||
|
||
### Structure du projet
|
||
```
|
||
ObsiGate/
|
||
├── backend/ # API FastAPI
|
||
│ ├── main.py # Endpoints, Pydantic models, rendu markdown
|
||
│ ├── indexer.py # Scan des vaults, index en mémoire, lookup table
|
||
│ ├── search.py # Moteur de recherche fulltext avec scoring
|
||
│ └── requirements.txt
|
||
├── frontend/ # Interface web (Vanilla JS, zéro framework)
|
||
│ ├── index.html # Page SPA + modales (aide, config, éditeur)
|
||
│ ├── app.js # Logique SPA, gestion d'état, API client
|
||
│ └── style.css # Styles (CSS variables, thèmes, responsive)
|
||
├── Dockerfile # Multi-stage, healthcheck, non-root
|
||
├── docker-compose.yml # Déploiement avec healthcheck
|
||
├── build.sh # Build multi-platform (amd64/arm64/arm/v7/i386)
|
||
└── CONTRIBUTING.md # Guide de contribution
|
||
```
|
||
|
||
### Contribuer
|
||
|
||
Voir [CONTRIBUTING.md](CONTRIBUTING.md) pour les détails.
|
||
|
||
---
|
||
|
||
## 📄 Licence
|
||
|
||
Ce projet est sous licence **MIT** - voir le fichier [LICENSE](LICENSE) pour les détails.
|
||
|
||
---
|
||
|
||
## 🤝 Support
|
||
|
||
- **Issues** : [git.dracodev.net/Projets/ObsiGate/issues](https://git.dracodev.net/Projets/ObsiGate/issues)
|
||
- **Documentation** : [git.dracodev.net/Projets/ObsiGate/wiki](https://git.dracodev.net/Projets/ObsiGate/wiki)
|
||
- **Auteur** : Bruno Beloeil
|
||
|
||
---
|
||
|
||
## 📝 Changelog
|
||
|
||
### v1.1.0 (2025)
|
||
|
||
**Sécurité**
|
||
- Protection path traversal sur tous les endpoints fichier
|
||
- Utilisateur non-root dans le conteneur Docker
|
||
- Dockerfile multi-stage (élimination des outils de build)
|
||
|
||
**Performance**
|
||
- Recherche fulltext en mémoire (zéro I/O disque par requête)
|
||
- Table de lookup O(1) pour la résolution des wikilinks
|
||
- Renderer mistune mis en cache (singleton)
|
||
- Scoring multi-facteurs (titre, chemin, tags, fréquence)
|
||
- `lucide.createIcons()` batché via `requestAnimationFrame`
|
||
- `AbortController` sur les requêtes de recherche
|
||
|
||
**Robustesse**
|
||
- Swap atomique de l'index (thread-safe) pendant le reload
|
||
- Extraction des tags inline (#tag) depuis le contenu markdown
|
||
- Modèles Pydantic sur tous les endpoints API
|
||
- Gestion d'erreurs avec toasts utilisateur (frontend)
|
||
- États de chargement pour la sidebar et le contenu
|
||
- Remplacement de `on_event` déprécié par `lifespan`
|
||
|
||
**Infrastructure**
|
||
- Endpoint `/api/health` pour monitoring
|
||
- Healthcheck Docker (Dockerfile + docker-compose)
|
||
- `build.sh` amélioré (variable version, checks, couleurs)
|
||
|
||
**Documentation**
|
||
- Docstrings complètes sur toutes les fonctions Python
|
||
- Schémas Pydantic documentés (Swagger UI auto-générée)
|
||
- README : sections Architecture, Performance, Sécurité, Changelog
|
||
- CONTRIBUTING.md ajouté
|
||
|
||
### v1.0.0 (2025)
|
||
- Version initiale
|
||
|
||
---
|
||
|
||
*Projet : ObsiGate | Version : 1.1.0 | Dernière mise à jour : 2025*
|