Initial commit: Set up FastAPI application for Nextcloud API with configuration, controllers, and utility functions. Added .gitignore, README, and requirements files. Implemented endpoints for file listing, uploading, and health checks.
This commit is contained in:
commit
8d6ae6291e
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Fichiers de configuration sensibles
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Environnement virtuel Python
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
274
README.md
Normal file
274
README.md
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
# PyNextcloud - API FastAPI pour Nextcloud
|
||||||
|
|
||||||
|
API REST construite avec FastAPI pour interagir avec un serveur Nextcloud et lister les fichiers et dossiers.
|
||||||
|
|
||||||
|
## 📋 Prérequis
|
||||||
|
|
||||||
|
- Python 3.8 ou supérieur
|
||||||
|
- Un serveur Nextcloud avec accès API
|
||||||
|
- Identifiants Nextcloud (username et password)
|
||||||
|
|
||||||
|
## 🚀 Installation
|
||||||
|
|
||||||
|
### 1. Créer un environnement virtuel
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Créer l'environnement virtuel
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# Activer l'environnement virtuel
|
||||||
|
.\venv\Scripts\Activate.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Installer les dépendances
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configuration
|
||||||
|
|
||||||
|
Créez un fichier `.env` à la racine du projet (vous pouvez copier `env.example`) :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Copy-Item env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifiez le fichier `.env` avec vos informations Nextcloud :
|
||||||
|
|
||||||
|
```env
|
||||||
|
NEXTCLOUD_URL=https://votre-serveur-nextcloud.com
|
||||||
|
NEXTCLOUD_USERNAME=votre_username
|
||||||
|
NEXTCLOUD_PASSWORD=votre_password
|
||||||
|
|
||||||
|
APP_HOST=0.0.0.0
|
||||||
|
APP_PORT=8000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Utilisation
|
||||||
|
|
||||||
|
### Démarrer le serveur
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Méthode 1 : Avec uvicorn directement
|
||||||
|
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||||
|
|
||||||
|
# Méthode 2 : Avec Python
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Le serveur démarre sur `http://localhost:8000`
|
||||||
|
|
||||||
|
### Documentation interactive
|
||||||
|
|
||||||
|
Une fois le serveur démarré, accédez à :
|
||||||
|
- Documentation Swagger UI : `http://localhost:8000/docs`
|
||||||
|
- Documentation ReDoc : `http://localhost:8000/redoc`
|
||||||
|
|
||||||
|
## 📡 Endpoints disponibles
|
||||||
|
|
||||||
|
### 1. Route racine
|
||||||
|
```
|
||||||
|
GET /
|
||||||
|
```
|
||||||
|
Retourne un message d'accueil et le lien vers la documentation.
|
||||||
|
|
||||||
|
### 2. Health Check
|
||||||
|
```
|
||||||
|
GET /health
|
||||||
|
```
|
||||||
|
Vérifie l'état de l'API et la connexion à Nextcloud.
|
||||||
|
|
||||||
|
**Réponse exemple :**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"nextcloud_connected": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Lister un répertoire
|
||||||
|
```
|
||||||
|
GET /list/{path}
|
||||||
|
```
|
||||||
|
Liste tous les fichiers et dossiers d'un répertoire Nextcloud.
|
||||||
|
|
||||||
|
**Paramètres :**
|
||||||
|
- `path` : Chemin du répertoire (utilisez `/` pour la racine)
|
||||||
|
|
||||||
|
**Exemples :**
|
||||||
|
```powershell
|
||||||
|
# Lister la racine
|
||||||
|
curl http://localhost:8000/list/
|
||||||
|
|
||||||
|
# Lister le dossier Documents
|
||||||
|
curl http://localhost:8000/list/Documents
|
||||||
|
|
||||||
|
# Lister un sous-dossier
|
||||||
|
curl http://localhost:8000/list/Documents/MonDossier
|
||||||
|
```
|
||||||
|
|
||||||
|
**Réponse exemple :**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"path": "/Documents",
|
||||||
|
"total_items": 5,
|
||||||
|
"summary": {
|
||||||
|
"directories": 2,
|
||||||
|
"files": 3
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "rapport.pdf",
|
||||||
|
"path": "/Documents/rapport.pdf",
|
||||||
|
"is_dir": false,
|
||||||
|
"size": 1024567,
|
||||||
|
"content_type": "application/pdf",
|
||||||
|
"last_modified": "2025-10-20T10:30:00",
|
||||||
|
"etag": "abc123"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Images",
|
||||||
|
"path": "/Documents/Images",
|
||||||
|
"is_dir": true,
|
||||||
|
"size": 0,
|
||||||
|
"content_type": null,
|
||||||
|
"last_modified": "2025-10-19T15:20:00",
|
||||||
|
"etag": "def456"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Informations sur un fichier/dossier
|
||||||
|
```
|
||||||
|
GET /info/{path}
|
||||||
|
```
|
||||||
|
Obtient les informations détaillées d'un fichier ou dossier spécifique.
|
||||||
|
|
||||||
|
**Exemples :**
|
||||||
|
```powershell
|
||||||
|
curl http://localhost:8000/info/Documents/rapport.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Upload un fichier
|
||||||
|
```
|
||||||
|
POST /upload
|
||||||
|
```
|
||||||
|
Upload un fichier vers un dossier Nextcloud.
|
||||||
|
|
||||||
|
**Paramètres (form-data) :**
|
||||||
|
- `file` : Le fichier à uploader
|
||||||
|
- `path` : Chemin du dossier de destination (ex: `/Documents`)
|
||||||
|
- `filename` : Nom du fichier (optionnel, utilise le nom original si non spécifié)
|
||||||
|
|
||||||
|
**Exemples :**
|
||||||
|
```powershell
|
||||||
|
# Upload un fichier avec PowerShell
|
||||||
|
$file = Get-Item "C:\chemin\vers\monfichier.pdf"
|
||||||
|
$form = @{
|
||||||
|
file = $file
|
||||||
|
path = "/Documents"
|
||||||
|
}
|
||||||
|
Invoke-WebRequest -Uri "http://localhost:8000/upload" -Method POST -Form $form
|
||||||
|
|
||||||
|
# Upload avec un nom personnalisé
|
||||||
|
$form = @{
|
||||||
|
file = Get-Item "C:\chemin\vers\monfichier.pdf"
|
||||||
|
path = "/Documents"
|
||||||
|
filename = "nouveau_nom.pdf"
|
||||||
|
}
|
||||||
|
Invoke-WebRequest -Uri "http://localhost:8000/upload" -Method POST -Form $form
|
||||||
|
```
|
||||||
|
|
||||||
|
**Réponse exemple :**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Fichier uploadé avec succès",
|
||||||
|
"file": {
|
||||||
|
"name": "rapport.pdf",
|
||||||
|
"path": "/Documents/rapport.pdf",
|
||||||
|
"size": 1024567,
|
||||||
|
"content_type": "application/pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Structure du projet
|
||||||
|
|
||||||
|
```
|
||||||
|
PyNextcloud/
|
||||||
|
│
|
||||||
|
├── main.py # Application FastAPI avec auto-chargement des contrôleurs
|
||||||
|
├── config.py # Configuration de l'application
|
||||||
|
├── utils.py # Fonctions utilitaires
|
||||||
|
├── controllers/ # 📁 Dossier des contrôleurs (auto-chargés)
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── root.py # Route racine
|
||||||
|
│ ├── health.py # Health check
|
||||||
|
│ ├── list.py # Lister les fichiers/dossiers
|
||||||
|
│ ├── info.py # Informations d'un fichier/dossier
|
||||||
|
│ └── upload.py # Upload de fichiers
|
||||||
|
├── requirements.txt # Dépendances Python
|
||||||
|
├── env.example # Exemple de fichier de configuration
|
||||||
|
├── .env # Configuration (à créer, non versionné)
|
||||||
|
└── README.md # Ce fichier
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎯 Architecture modulaire avec auto-chargement
|
||||||
|
|
||||||
|
Le projet utilise une architecture modulaire où tous les contrôleurs dans le dossier `controllers/` sont **automatiquement chargés** au démarrage.
|
||||||
|
|
||||||
|
**Pour ajouter un nouveau endpoint :**
|
||||||
|
1. Créez un nouveau fichier dans `controllers/`, par exemple `controllers/mon_endpoint.py`
|
||||||
|
2. Créez un `router` FastAPI dans ce fichier
|
||||||
|
3. C'est tout ! Le contrôleur sera automatiquement chargé au démarrage
|
||||||
|
|
||||||
|
**Exemple de nouveau contrôleur :**
|
||||||
|
```python
|
||||||
|
# controllers/mon_endpoint.py
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/mon-endpoint")
|
||||||
|
async def mon_endpoint():
|
||||||
|
return {"message": "Mon nouveau endpoint"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Dépendances principales
|
||||||
|
|
||||||
|
- **FastAPI** : Framework web moderne et rapide
|
||||||
|
- **Uvicorn** : Serveur ASGI
|
||||||
|
- **nc-py-api** : Client Python pour l'API Nextcloud
|
||||||
|
- **python-dotenv** : Gestion des variables d'environnement
|
||||||
|
- **pydantic** : Validation des données
|
||||||
|
|
||||||
|
## ⚠️ Notes importantes
|
||||||
|
|
||||||
|
1. **Sécurité** : Ne committez jamais votre fichier `.env` dans Git. Il contient vos identifiants.
|
||||||
|
2. **Chemins** : Les chemins doivent être relatifs à la racine de votre espace Nextcloud.
|
||||||
|
3. **Authentification** : L'API utilise l'authentification basique avec username/password.
|
||||||
|
|
||||||
|
## 🐛 Dépannage
|
||||||
|
|
||||||
|
### Erreur de connexion à Nextcloud
|
||||||
|
- Vérifiez que l'URL de votre serveur Nextcloud est correcte
|
||||||
|
- Vérifiez vos identifiants
|
||||||
|
- Assurez-vous que votre serveur Nextcloud est accessible
|
||||||
|
|
||||||
|
### Erreur 404 sur un répertoire
|
||||||
|
- Vérifiez que le chemin existe dans votre Nextcloud
|
||||||
|
- Les chemins sont sensibles à la casse
|
||||||
|
|
||||||
|
### Problèmes d'activation de l'environnement virtuel
|
||||||
|
Si vous avez une erreur de sécurité PowerShell, exécutez :
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Licence
|
||||||
|
|
||||||
|
Ce projet est libre d'utilisation pour vos besoins personnels et professionnels.
|
||||||
|
|
||||||
22
config.py
Normal file
22
config.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""Configuration de l'application"""
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Charger les variables d'environnement
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
"""Configuration de l'application"""
|
||||||
|
nextcloud_url: str
|
||||||
|
nextcloud_username: str
|
||||||
|
nextcloud_password: str
|
||||||
|
app_host: str = "0.0.0.0"
|
||||||
|
app_port: int = 8000
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(env_file=".env")
|
||||||
|
|
||||||
|
|
||||||
|
# Instance globale des paramètres
|
||||||
|
settings = Settings()
|
||||||
|
|
||||||
2
controllers/__init__.py
Normal file
2
controllers/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"""Package des contrôleurs de l'application"""
|
||||||
|
|
||||||
19
controllers/debug.py
Normal file
19
controllers/debug.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"""Contrôleur de débogage pour lister les routes disponibles"""
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/debug/routes")
|
||||||
|
async def list_routes(request: Request):
|
||||||
|
"""Liste toutes les routes disponibles dans l'application"""
|
||||||
|
routes = []
|
||||||
|
for route in request.app.routes:
|
||||||
|
if hasattr(route, "methods") and hasattr(route, "path"):
|
||||||
|
routes.append({
|
||||||
|
"path": route.path,
|
||||||
|
"methods": list(route.methods),
|
||||||
|
"name": route.name
|
||||||
|
})
|
||||||
|
return {"total_routes": len(routes), "routes": routes}
|
||||||
|
|
||||||
22
controllers/health.py
Normal file
22
controllers/health.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""Contrôleur pour vérifier l'état de l'application"""
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from utils import get_nextcloud_client
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
"""Vérification de l'état de l'API"""
|
||||||
|
try:
|
||||||
|
nc = get_nextcloud_client()
|
||||||
|
# Test de connexion
|
||||||
|
nc.users.get_list()
|
||||||
|
return {"status": "healthy", "nextcloud_connected": True}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "unhealthy",
|
||||||
|
"nextcloud_connected": False,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
56
controllers/info.py
Normal file
56
controllers/info.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"""Contrôleur pour obtenir les informations d'un fichier/dossier"""
|
||||||
|
from fastapi import APIRouter, HTTPException, Path
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from nc_py_api import NextcloudException
|
||||||
|
from utils import get_nextcloud_client, format_file_info
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/info/{path:path}")
|
||||||
|
async def file_info(
|
||||||
|
path: str = Path(
|
||||||
|
...,
|
||||||
|
description="Chemin du fichier/dossier",
|
||||||
|
examples=["Documents/fichier.txt"]
|
||||||
|
)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Obtient les informations détaillées d'un fichier ou dossier
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: Chemin du fichier/dossier
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON contenant les informations du fichier
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
nc = get_nextcloud_client()
|
||||||
|
|
||||||
|
# Normaliser le chemin
|
||||||
|
file_path = "/" + path.strip("/")
|
||||||
|
|
||||||
|
try:
|
||||||
|
file_info_data = nc.files.by_path(file_path)
|
||||||
|
except NextcloudException as e:
|
||||||
|
if "404" in str(e):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Le fichier/dossier '{file_path}' n'existe pas"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Erreur Nextcloud: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return JSONResponse(content=format_file_info(file_info_data))
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Erreur interne: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
65
controllers/list.py
Normal file
65
controllers/list.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"""Contrôleur pour lister les fichiers et dossiers Nextcloud"""
|
||||||
|
from fastapi import APIRouter, HTTPException, Path
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from nc_py_api import NextcloudException
|
||||||
|
from utils import get_nextcloud_client, format_file_info
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/list/{path:path}")
|
||||||
|
async def list_directory(
|
||||||
|
path: str = Path(
|
||||||
|
...,
|
||||||
|
description="Chemin du répertoire à lister (utilisez '/' pour la racine)",
|
||||||
|
examples=["Documents"]
|
||||||
|
)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Liste tous les fichiers et dossiers d'un répertoire Nextcloud
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: Chemin du répertoire (sans le slash initial)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON contenant la liste des fichiers et dossiers
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Créer le client Nextcloud
|
||||||
|
nc = get_nextcloud_client()
|
||||||
|
|
||||||
|
# Normaliser le chemin
|
||||||
|
if path == "/" or path == "":
|
||||||
|
directory_path = "/"
|
||||||
|
else:
|
||||||
|
# Enlever les slashes de début/fin
|
||||||
|
directory_path = "/" + path.strip("/")
|
||||||
|
|
||||||
|
# Lister le contenu du répertoire
|
||||||
|
try:
|
||||||
|
files_list = nc.files.listdir(path=directory_path)
|
||||||
|
except NextcloudException as e:
|
||||||
|
if "404" in str(e):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Le répertoire '{directory_path}' n'existe pas"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Erreur Nextcloud: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Formater les résultats - retourner uniquement la liste des items
|
||||||
|
items = [format_file_info(item) for item in files_list]
|
||||||
|
|
||||||
|
return JSONResponse(content=items)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Erreur interne: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
14
controllers/root.py
Normal file
14
controllers/root.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
"""Contrôleur pour la route racine"""
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
async def root():
|
||||||
|
"""Route racine"""
|
||||||
|
return {
|
||||||
|
"message": "API Nextcloud - Utilisez /list/{path} pour lister les fichiers",
|
||||||
|
"documentation": "/docs"
|
||||||
|
}
|
||||||
|
|
||||||
107
controllers/upload.py
Normal file
107
controllers/upload.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
"""Contrôleur pour uploader des fichiers vers Nextcloud"""
|
||||||
|
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from nc_py_api import NextcloudException
|
||||||
|
from utils import get_nextcloud_client
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upload")
|
||||||
|
async def upload_file(
|
||||||
|
file: UploadFile = File(..., description="Fichier à uploader"),
|
||||||
|
path: str = Form(..., description="Chemin du dossier de destination (ex: /Documents)"),
|
||||||
|
filename: Optional[str] = Form(None, description="Nom du fichier (optionnel, utilise le nom original si non spécifié)")
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Upload un fichier vers un dossier Nextcloud
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file: Le fichier à uploader
|
||||||
|
path: Chemin du dossier de destination
|
||||||
|
filename: Nom du fichier (optionnel)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON contenant les informations du fichier uploadé
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Créer le client Nextcloud
|
||||||
|
nc = get_nextcloud_client()
|
||||||
|
|
||||||
|
# Déterminer le nom du fichier
|
||||||
|
final_filename = filename if filename else file.filename
|
||||||
|
|
||||||
|
# Normaliser le chemin de destination
|
||||||
|
if path == "/" or path == "":
|
||||||
|
destination_path = f"/{final_filename}"
|
||||||
|
directory_path = "/"
|
||||||
|
else:
|
||||||
|
# Enlever les slashes de début/fin et ajouter le nom du fichier
|
||||||
|
clean_path = path.strip("/")
|
||||||
|
destination_path = f"/{clean_path}/{final_filename}"
|
||||||
|
directory_path = f"/{clean_path}"
|
||||||
|
|
||||||
|
# Vérifier si le dossier existe, sinon le créer
|
||||||
|
if directory_path != "/":
|
||||||
|
try:
|
||||||
|
# Vérifier si le dossier existe
|
||||||
|
nc.files.by_path(directory_path)
|
||||||
|
except NextcloudException as e:
|
||||||
|
if "404" in str(e):
|
||||||
|
# Le dossier n'existe pas, on le crée
|
||||||
|
try:
|
||||||
|
nc.files.mkdir(directory_path)
|
||||||
|
print(f"📁 Dossier créé : {directory_path}")
|
||||||
|
except Exception as mkdir_error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Impossible de créer le dossier '{directory_path}': {str(mkdir_error)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Lire le contenu du fichier
|
||||||
|
file_content = await file.read()
|
||||||
|
|
||||||
|
# Uploader le fichier vers Nextcloud
|
||||||
|
try:
|
||||||
|
nc.files.upload(
|
||||||
|
path=destination_path,
|
||||||
|
content=file_content
|
||||||
|
)
|
||||||
|
except NextcloudException as e:
|
||||||
|
if "409" in str(e) or "412" in str(e):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=409,
|
||||||
|
detail=f"Le fichier '{final_filename}' existe déjà"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Erreur Nextcloud: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Retourner les informations du fichier uploadé
|
||||||
|
result = {
|
||||||
|
"success": True,
|
||||||
|
"message": "Fichier uploadé avec succès",
|
||||||
|
"file": {
|
||||||
|
"name": final_filename,
|
||||||
|
"path": destination_path,
|
||||||
|
"size": len(file_content),
|
||||||
|
"content_type": file.content_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONResponse(content=result, status_code=201)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Erreur interne: {str(e)}"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
# Fermer le fichier uploadé
|
||||||
|
await file.close()
|
||||||
|
|
||||||
9
env.example
Normal file
9
env.example
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Configuration du serveur Nextcloud
|
||||||
|
NEXTCLOUD_URL=https://votre-serveur-nextcloud.com
|
||||||
|
NEXTCLOUD_USERNAME=votre_username
|
||||||
|
NEXTCLOUD_PASSWORD=votre_password
|
||||||
|
|
||||||
|
# Configuration de l'application
|
||||||
|
APP_HOST=0.0.0.0
|
||||||
|
APP_PORT=8000
|
||||||
|
|
||||||
57
main.py
Normal file
57
main.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
"""Application FastAPI principale avec auto-chargement des contrôleurs"""
|
||||||
|
from fastapi import FastAPI
|
||||||
|
import uvicorn
|
||||||
|
from pathlib import Path
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
from config import settings
|
||||||
|
|
||||||
|
# Créer l'application FastAPI
|
||||||
|
app = FastAPI(
|
||||||
|
title="Nextcloud API",
|
||||||
|
description="API pour lister les fichiers et dossiers d'un serveur Nextcloud",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def auto_load_controllers():
|
||||||
|
"""Charge automatiquement tous les contrôleurs depuis le dossier controllers"""
|
||||||
|
controllers_dir = Path(__file__).parent / "controllers"
|
||||||
|
|
||||||
|
if not controllers_dir.exists():
|
||||||
|
print("⚠️ Le dossier 'controllers' n'existe pas")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Parcourir tous les fichiers Python dans le dossier controllers
|
||||||
|
for file_path in controllers_dir.glob("*.py"):
|
||||||
|
# Ignorer __init__.py
|
||||||
|
if file_path.stem == "__init__":
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Importer le module dynamiquement
|
||||||
|
module_name = f"controllers.{file_path.stem}"
|
||||||
|
module = importlib.import_module(module_name)
|
||||||
|
|
||||||
|
# Vérifier si le module a un attribut 'router'
|
||||||
|
if hasattr(module, "router"):
|
||||||
|
app.include_router(module.router)
|
||||||
|
print(f"✅ Contrôleur chargé : {file_path.stem}")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Le fichier {file_path.stem}.py n'a pas de 'router'")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Erreur lors du chargement de {file_path.stem}: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
# Auto-charger tous les contrôleurs au démarrage
|
||||||
|
auto_load_controllers()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run(
|
||||||
|
"main:app",
|
||||||
|
host=settings.app_host,
|
||||||
|
port=settings.app_port,
|
||||||
|
reload=True
|
||||||
|
)
|
||||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fastapi>=0.109.2
|
||||||
|
uvicorn[standard]>=0.24.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
nc-py-api>=0.21.1
|
||||||
|
pydantic>=2.5.0
|
||||||
|
pydantic-settings>=2.1.0
|
||||||
|
python-multipart>=0.0.6
|
||||||
|
|
||||||
54
utils.py
Normal file
54
utils.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""Fonctions utilitaires pour l'application"""
|
||||||
|
from typing import Dict, Any
|
||||||
|
from fastapi import HTTPException
|
||||||
|
from nc_py_api import Nextcloud
|
||||||
|
from config import settings
|
||||||
|
|
||||||
|
|
||||||
|
def get_nextcloud_client() -> Nextcloud:
|
||||||
|
"""Crée et retourne un client Nextcloud"""
|
||||||
|
try:
|
||||||
|
nc = Nextcloud(
|
||||||
|
nextcloud_url=settings.nextcloud_url,
|
||||||
|
nc_auth_user=settings.nextcloud_username,
|
||||||
|
nc_auth_pass=settings.nextcloud_password
|
||||||
|
)
|
||||||
|
return nc
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Erreur de connexion à Nextcloud: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def format_file_info(file_info) -> Dict[str, Any]:
|
||||||
|
"""Formate les informations d'un fichier/dossier"""
|
||||||
|
result = {
|
||||||
|
"name": getattr(file_info, 'name', ''),
|
||||||
|
"path": getattr(file_info, 'user_path', getattr(file_info, 'path', '')),
|
||||||
|
"is_dir": getattr(file_info, 'is_dir', False),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attributs optionnels
|
||||||
|
if hasattr(file_info, 'info'):
|
||||||
|
info = file_info.info
|
||||||
|
result["size"] = getattr(info, 'size', 0)
|
||||||
|
result["content_type"] = getattr(info, 'mimetype', None)
|
||||||
|
if hasattr(info, 'last_modified'):
|
||||||
|
result["last_modified"] = info.last_modified.isoformat() if info.last_modified else None
|
||||||
|
else:
|
||||||
|
result["last_modified"] = None
|
||||||
|
result["etag"] = getattr(info, 'etag', None)
|
||||||
|
else:
|
||||||
|
result["size"] = getattr(file_info, 'size', 0)
|
||||||
|
result["content_type"] = getattr(file_info, 'mimetype', getattr(file_info, 'content_type', None))
|
||||||
|
if hasattr(file_info, 'last_modified') and file_info.last_modified:
|
||||||
|
result["last_modified"] = file_info.last_modified.isoformat()
|
||||||
|
else:
|
||||||
|
result["last_modified"] = None
|
||||||
|
result["etag"] = getattr(file_info, 'etag', None)
|
||||||
|
|
||||||
|
result["file_id"] = getattr(file_info, 'file_id', None)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user