From 6f63821728da12de0953ff745e47d5036e220428 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 9 Sep 2025 00:35:32 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Ajout=20version=20simplifi=C3=A9e=20san?= =?UTF-8?q?s=20IA=20et=20correction=20des=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout docker-compose.simple.yml avec ports modifiés (15432, 16379, 19000, 19001, 18000) - Création app_simple.py sans dépendances IA - Ajout Dockerfile.simple et requirements.simple.txt - Correction attribut metadata réservé dans database.py - Ajout scripts de démarrage et test simplifiés - Configuration .env.simple pour version sans IA --- Makefile.simple | 67 ++++++++ docker/host-api/Dockerfile.simple | 12 ++ docker/host-api/requirements.simple.txt | 9 ++ infra/.env.simple | 21 +++ infra/docker-compose.simple.yml | 83 ++++++++++ infra/docker-compose.yml | 4 +- ops/start-simple.sh | 41 +++++ services/host_api/app_simple.py | 202 ++++++++++++++++++++++++ services/host_api/domain/database.py | 2 +- test-simple.sh | 18 +++ 10 files changed, 455 insertions(+), 4 deletions(-) create mode 100644 Makefile.simple create mode 100644 docker/host-api/Dockerfile.simple create mode 100644 docker/host-api/requirements.simple.txt create mode 100644 infra/.env.simple create mode 100644 infra/docker-compose.simple.yml create mode 100755 ops/start-simple.sh create mode 100644 services/host_api/app_simple.py create mode 100755 test-simple.sh diff --git a/Makefile.simple b/Makefile.simple new file mode 100644 index 0000000..24f7b89 --- /dev/null +++ b/Makefile.simple @@ -0,0 +1,67 @@ +SHELL := /bin/bash +ENV ?= infra/.env + +# Charger les variables d'environnement +include $(ENV) +export + +.PHONY: help up down start-simple logs ps clean restart + +help: ## Afficher l'aide + @echo "Commandes disponibles :" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +up: ## Démarrer tous les services (version complète) + cd infra && docker compose up -d + +up-simple: ## Démarrer les services simplifiés (sans IA) + cd infra && docker compose -f docker-compose.simple.yml up -d + +down: ## Arrêter tous les services + cd infra && docker compose down + +down-simple: ## Arrêter les services simplifiés + cd infra && docker compose -f docker-compose.simple.yml down + +start-simple: ## Initialiser l'infrastructure simplifiée + bash ops/start-simple.sh + +logs: ## Afficher les logs + cd infra && docker compose logs -f --tail=200 + +logs-simple: ## Afficher les logs (version simplifiée) + cd infra && docker compose -f docker-compose.simple.yml logs -f --tail=200 + +ps: ## Afficher le statut des services + cd infra && docker compose ps + +ps-simple: ## Afficher le statut des services (version simplifiée) + cd infra && docker compose -f docker-compose.simple.yml ps + +clean: ## Nettoyer les volumes et images + cd infra && docker compose down -v + docker system prune -f + +restart: ## Redémarrer tous les services + cd infra && docker compose restart + +build: ## Reconstruire les images + cd infra && docker compose build --no-cache + +build-simple: ## Reconstruire les images (version simplifiée) + cd infra && docker compose -f docker-compose.simple.yml build --no-cache + +test-api: ## Tester l'API + curl -F "file=@tests/data/sample.pdf" \ + -F "id_dossier=D-2025-001" \ + -F "source=upload" \ + -F "etude_id=E-001" \ + -F "utilisateur_id=U-123" \ + http://localhost:8000/api/import + +status: ## Vérifier le statut de tous les services + @echo "=== Statut des services ===" + @make ps-simple + @echo "" + @echo "=== Test de connectivité ===" + @curl -s http://localhost:8000/api/health || echo "API non accessible" diff --git a/docker/host-api/Dockerfile.simple b/docker/host-api/Dockerfile.simple new file mode 100644 index 0000000..6163acd --- /dev/null +++ b/docker/host-api/Dockerfile.simple @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +RUN apt-get update && apt-get install -y libmagic1 && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY docker/host-api/requirements.simple.txt requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +COPY services/host_api /app + +CMD ["uvicorn", "app_simple:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker/host-api/requirements.simple.txt b/docker/host-api/requirements.simple.txt new file mode 100644 index 0000000..b63103d --- /dev/null +++ b/docker/host-api/requirements.simple.txt @@ -0,0 +1,9 @@ +fastapi==0.115.0 +uvicorn[standard]==0.30.6 +pydantic==2.8.2 +sqlalchemy==2.0.35 +psycopg[binary]==3.2.1 +minio==7.2.7 +redis==5.0.7 +python-multipart==0.0.9 +requests==2.32.3 diff --git a/infra/.env.simple b/infra/.env.simple new file mode 100644 index 0000000..1358026 --- /dev/null +++ b/infra/.env.simple @@ -0,0 +1,21 @@ +# Configuration simplifiée sans IA +PROJECT_NAME=notariat +DOMAIN=localhost +TZ=Europe/Paris + +# Base de données PostgreSQL +POSTGRES_USER=notariat +POSTGRES_PASSWORD=notariat_pwd +POSTGRES_DB=notariat + +# Redis +REDIS_PASSWORD= + +# MinIO (stockage objet) +MINIO_ROOT_USER=minio +MINIO_ROOT_PASSWORD=minio_pwd +MINIO_BUCKET=ingest + +# Configuration de développement +DEBUG=true +LOG_LEVEL=debug diff --git a/infra/docker-compose.simple.yml b/infra/docker-compose.simple.yml new file mode 100644 index 0000000..b27bf35 --- /dev/null +++ b/infra/docker-compose.simple.yml @@ -0,0 +1,83 @@ +x-env: &default-env + TZ: ${TZ} + PUID: "1000" + PGID: "1000" + +services: + postgres: + image: postgres:16 + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "15432:5432" + restart: unless-stopped + + redis: + image: redis:7 + command: ["redis-server", "--appendonly", "yes"] + volumes: + - redis:/data + ports: + - "16379:6379" + restart: unless-stopped + + minio: + image: minio/minio:latest + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} + volumes: + - minio:/data + ports: + - "19000:9000" + - "19001:9001" + restart: unless-stopped + + host-api: + image: notariat-api-simple + env_file: ./.env + environment: + <<: *default-env + DATABASE_URL: postgresql+psycopg://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_DB + REDIS_URL: redis://redis:6379/0 + MINIO_ENDPOINT: http://minio:9000 + MINIO_BUCKET: ${MINIO_BUCKET} + volumes: + - ../services/host_api:/app + - ../ops/seed:/seed:ro + - ../ops/seed/schemas:/schemas:ro + ports: + - "18000:8000" + depends_on: + - postgres + - redis + - minio + restart: unless-stopped + +# Worker désactivé pour la version simplifiée + # worker: + # build: + # context: ../docker/worker + # env_file: ./.env + # environment: + # <<: *default-env + # DATABASE_URL: postgresql+psycopg://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_DB + # REDIS_URL: redis://redis:6379/0 + # MINIO_ENDPOINT: http://minio:9000 + # MINIO_BUCKET: ${MINIO_BUCKET} + # volumes: + # - ../services/worker:/app + # - ../ops/seed:/seed:ro + # depends_on: + # - host-api + # restart: unless-stopped + +volumes: + pgdata: + redis: + minio: diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 225216a..2371c26 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.9" - x-env: &default-env TZ: ${TZ} PUID: "1000" @@ -25,7 +23,7 @@ services: restart: unless-stopped minio: - image: minio/minio:RELEASE.2025-01-13T00-00-00Z + image: minio/minio:latest command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER} diff --git a/ops/start-simple.sh b/ops/start-simple.sh new file mode 100755 index 0000000..bc3a463 --- /dev/null +++ b/ops/start-simple.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -euo pipefail + +echo "Démarrage du pipeline notarial simplifié (sans IA)..." + +# Aller dans le répertoire infra +cd "$(dirname "$0")/../infra" + +# Copier le fichier d'environnement simplifié +cp -n .env.simple .env || true +echo "Fichier .env créé à partir de .env.simple" + +# Télécharger les images Docker +echo "Téléchargement des images Docker..." +docker compose -f docker-compose.simple.yml pull + +# Démarrer les services de base +echo "Démarrage des services de base..." +docker compose -f docker-compose.simple.yml up -d postgres redis minio + +# Attendre que les services soient prêts +echo "Attente du démarrage des services..." +sleep 10 + +# Configuration MinIO +echo "Configuration de MinIO..." +# Créer l'alias MinIO +mc alias set local http://127.0.0.1:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD || true +# Créer le bucket +mc mb -p local/$MINIO_BUCKET || true + +# Démarrer les services applicatifs +echo "Démarrage des services applicatifs..." +docker compose -f docker-compose.simple.yml up -d host-api worker + +echo "Démarrage terminé !" +echo "Services disponibles :" +echo "- API: http://localhost:8000/api" +echo "- MinIO Console: http://localhost:9001" +echo "- PostgreSQL: localhost:5432" +echo "- Redis: localhost:6379" diff --git a/services/host_api/app_simple.py b/services/host_api/app_simple.py new file mode 100644 index 0000000..3ced132 --- /dev/null +++ b/services/host_api/app_simple.py @@ -0,0 +1,202 @@ +""" +API d'ingestion simplifiée pour le pipeline notarial (sans IA) +""" +from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Depends +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +import uuid +import time +import os +import logging + +from domain.models import ImportMeta, DocumentStatus +from domain.database import get_db, init_db +from routes import health +from utils.storage import store_document + +# Configuration du logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +app = FastAPI( + title="Notariat Pipeline API (Simplifié)", + description="API d'ingestion simplifiée pour le traitement de documents notariaux (sans IA)", + version="1.0.0-simple" +) + +# Configuration CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # À restreindre en production + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Inclusion des routes +app.include_router(health.router, prefix="/api", tags=["health"]) + +@app.on_event("startup") +async def startup_event(): + """Initialisation au démarrage de l'application""" + logger.info("Démarrage de l'API Notariat Pipeline (Simplifié)") + await init_db() + +@app.on_event("shutdown") +async def shutdown_event(): + """Nettoyage à l'arrêt de l'application""" + logger.info("Arrêt de l'API Notariat Pipeline (Simplifié)") + +@app.exception_handler(Exception) +async def global_exception_handler(request, exc): + """Gestionnaire d'exceptions global""" + logger.error(f"Erreur non gérée: {exc}", exc_info=True) + return JSONResponse( + status_code=500, + content={"detail": "Erreur interne du serveur"} + ) + +@app.get("/") +async def root(): + """Point d'entrée principal""" + return { + "message": "API Notariat Pipeline (Simplifié)", + "version": "1.0.0-simple", + "status": "running", + "features": { + "ai_disabled": True, + "ocr_enabled": False, + "classification_enabled": False, + "extraction_enabled": False + } + } + +@app.post("/api/import") +async def import_document( + file: UploadFile = File(...), + id_dossier: str = Form(...), + source: str = Form("upload"), + etude_id: str = Form(...), + utilisateur_id: str = Form(...), + db = Depends(get_db) +): + """ + Import d'un nouveau document dans le pipeline (version simplifiée) + """ + try: + # Vérification du type de fichier + allowed_types = ["application/pdf", "image/jpeg", "image/png", "image/tiff"] + if file.content_type not in allowed_types: + raise HTTPException( + status_code=415, + detail=f"Type de fichier non supporté: {file.content_type}" + ) + + # Génération d'un ID unique + doc_id = str(uuid.uuid4()) + + # Lecture du contenu du fichier + content = await file.read() + file_size = len(content) + + # Stockage du document + storage_path = await store_document(doc_id, content, file.filename) + + # Création de l'enregistrement en base + from domain.database import Document + document = Document( + id=doc_id, + filename=file.filename or "unknown", + mime_type=file.content_type, + size=file_size, + status=DocumentStatus.PENDING.value, + id_dossier=id_dossier, + etude_id=etude_id, + utilisateur_id=utilisateur_id, + source=source + ) + + db.add(document) + db.commit() + db.refresh(document) + + logger.info(f"Document {doc_id} importé avec succès (version simplifiée)") + + return { + "status": "stored", + "id_document": doc_id, + "message": "Document stocké (traitement IA désactivé)", + "storage_path": storage_path + } + + except Exception as e: + logger.error(f"Erreur lors de l'import du document: {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@app.get("/api/documents/{document_id}") +async def get_document( + document_id: str, + db = Depends(get_db) +): + """ + Récupération des informations d'un document + """ + from domain.database import Document + document = db.query(Document).filter(Document.id == document_id).first() + + if not document: + raise HTTPException(status_code=404, detail="Document non trouvé") + + return { + "id": document.id, + "filename": document.filename, + "mime_type": document.mime_type, + "size": document.size, + "status": document.status, + "id_dossier": document.id_dossier, + "etude_id": document.etude_id, + "utilisateur_id": document.utilisateur_id, + "created_at": document.created_at, + "updated_at": document.updated_at, + "processing_steps": document.processing_steps, + "extracted_data": document.extracted_data, + "errors": document.errors + } + +@app.get("/api/documents") +async def list_documents( + etude_id: str = None, + id_dossier: str = None, + limit: int = 50, + offset: int = 0, + db = Depends(get_db) +): + """ + Liste des documents avec filtres + """ + from domain.database import Document + query = db.query(Document) + + if etude_id: + query = query.filter(Document.etude_id == etude_id) + + if id_dossier: + query = query.filter(Document.id_dossier == id_dossier) + + documents = query.offset(offset).limit(limit).all() + + return [ + { + "id": doc.id, + "filename": doc.filename, + "mime_type": doc.mime_type, + "size": doc.size, + "status": doc.status, + "id_dossier": doc.id_dossier, + "etude_id": doc.etude_id, + "utilisateur_id": doc.utilisateur_id, + "created_at": doc.created_at, + "updated_at": doc.updated_at + } + for doc in documents + ] diff --git a/services/host_api/domain/database.py b/services/host_api/domain/database.py index c905e5d..cecd985 100644 --- a/services/host_api/domain/database.py +++ b/services/host_api/domain/database.py @@ -52,7 +52,7 @@ class ProcessingLog(Base): completed_at = Column(DateTime(timezone=True)) duration = Column(Integer) # en millisecondes error_message = Column(Text) - metadata = Column(JSON, default={}) + step_metadata = Column(JSON, default={}) def get_db() -> Generator[Session, None, None]: """Dépendance pour obtenir une session de base de données""" diff --git a/test-simple.sh b/test-simple.sh new file mode 100755 index 0000000..2bba4d1 --- /dev/null +++ b/test-simple.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +echo "=== Test du pipeline notarial simplifié ===" + +# Test de santé de l'API +echo "1. Test de santé de l'API..." +curl -s http://localhost:8000/api/health | jq . || echo "API non accessible" + +echo "" +echo "2. Test de l'endpoint racine..." +curl -s http://localhost:8000/ | jq . || echo "Endpoint racine non accessible" + +echo "" +echo "3. Test de liste des documents..." +curl -s http://localhost:8000/api/documents | jq . || echo "Liste des documents non accessible" + +echo "" +echo "=== Tests terminés ==="