refactor(auth): reorganize imports and remove unused dependencies - Added some missing auth
This commit is contained in:
parent
18d72b3bf9
commit
18603ded6e
4 changed files with 35 additions and 432 deletions
293
api.py
293
api.py
|
|
@ -16,9 +16,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy import select
|
||||
import os
|
||||
from pathlib import Path as FilePath
|
||||
from data.data import TAGS_METADATA, templates_signature_email
|
||||
from data.data import TAGS_METADATA
|
||||
from config.config import settings
|
||||
from database import (
|
||||
User,
|
||||
init_db,
|
||||
async_session_factory,
|
||||
get_session,
|
||||
|
|
@ -58,7 +59,6 @@ from schemas import (
|
|||
FactureUpdate,
|
||||
LivraisonCreate,
|
||||
LivraisonUpdate,
|
||||
StatutSignature,
|
||||
ArticleCreate,
|
||||
Article,
|
||||
ArticleUpdate,
|
||||
|
|
@ -93,9 +93,10 @@ from core.sage_context import (
|
|||
from utils.generic_functions import (
|
||||
_preparer_lignes_document,
|
||||
universign_envoyer,
|
||||
universign_statut,
|
||||
)
|
||||
|
||||
from core.dependencies import get_current_user
|
||||
|
||||
if os.path.exists("/app"):
|
||||
LOGS_DIR = FilePath("/app/logs")
|
||||
else:
|
||||
|
|
@ -976,266 +977,6 @@ async def commande_vers_facture(
|
|||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
@app.get("/admin/signatures/relances-auto", tags=["Admin"])
|
||||
async def relancer_signatures_automatique(session: AsyncSession = Depends(get_session)):
|
||||
try:
|
||||
from datetime import timedelta
|
||||
|
||||
date_limite = datetime.now() - timedelta(days=7)
|
||||
|
||||
query = select(SignatureLog).where(
|
||||
SignatureLog.statut.in_(
|
||||
[StatutSignatureDB.EN_ATTENTE, StatutSignatureDB.ENVOYE]
|
||||
),
|
||||
SignatureLog.date_envoi < date_limite,
|
||||
SignatureLog.nb_relances < 3, # Max 3 relances
|
||||
)
|
||||
|
||||
result = await session.execute(query)
|
||||
signatures_a_relancer = result.scalars().all()
|
||||
|
||||
nb_relances = 0
|
||||
|
||||
for signature in signatures_a_relancer:
|
||||
try:
|
||||
nb_jours = (datetime.now() - signature.date_envoi).days
|
||||
jours_restants = 30 - nb_jours # Lien expire après 30 jours
|
||||
|
||||
if jours_restants <= 0:
|
||||
signature.statut = StatutSignatureDB.EXPIRE
|
||||
continue
|
||||
|
||||
template = templates_signature_email["relance_signature"]
|
||||
|
||||
type_labels = {
|
||||
0: "Devis",
|
||||
10: "Commande",
|
||||
30: "Bon de Livraison",
|
||||
60: "Facture",
|
||||
50: "Avoir",
|
||||
}
|
||||
|
||||
variables = {
|
||||
"NOM_SIGNATAIRE": signature.nom_signataire,
|
||||
"TYPE_DOC": type_labels.get(signature.type_document, "Document"),
|
||||
"NUMERO": signature.document_id,
|
||||
"NB_JOURS": str(nb_jours),
|
||||
"JOURS_RESTANTS": str(jours_restants),
|
||||
"SIGNER_URL": signature.signer_url,
|
||||
"CONTACT_EMAIL": settings.smtp_from,
|
||||
}
|
||||
|
||||
sujet = template["sujet"]
|
||||
corps = template["corps_html"]
|
||||
|
||||
for var, valeur in variables.items():
|
||||
sujet = sujet.replace(f"{{{{{var}}}}}", str(valeur))
|
||||
corps = corps.replace(f"{{{{{var}}}}}", str(valeur))
|
||||
|
||||
email_log = EmailLog(
|
||||
id=str(uuid.uuid4()),
|
||||
destinataire=signature.email_signataire,
|
||||
sujet=sujet,
|
||||
corps_html=corps,
|
||||
document_ids=signature.document_id,
|
||||
type_document=signature.type_document,
|
||||
statut=StatutEmailDB.EN_ATTENTE,
|
||||
date_creation=datetime.now(),
|
||||
nb_tentatives=0,
|
||||
)
|
||||
|
||||
session.add(email_log)
|
||||
email_queue.enqueue(email_log.id)
|
||||
|
||||
signature.est_relance = True
|
||||
signature.nb_relances = (signature.nb_relances or 0) + 1
|
||||
|
||||
nb_relances += 1
|
||||
|
||||
logger.info(
|
||||
f" Relance envoyée: {signature.document_id} ({signature.nb_relances}/3)"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur relance signature {signature.id}: {e}")
|
||||
continue
|
||||
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"signatures_verifiees": len(signatures_a_relancer),
|
||||
"relances_envoyees": nb_relances,
|
||||
"message": f"{nb_relances} email(s) de relance envoyé(s)",
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur relances automatiques: {e}")
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
@app.get("/signature/universign/status", tags=["Signatures"])
|
||||
async def statut_signature(docId: str = Query(...)):
|
||||
try:
|
||||
async with async_session_factory() as session:
|
||||
query = select(SignatureLog).where(SignatureLog.document_id == docId)
|
||||
result = await session.execute(query)
|
||||
signature_log = result.scalar_one_or_none()
|
||||
|
||||
if not signature_log:
|
||||
raise HTTPException(404, "Signature introuvable")
|
||||
|
||||
statut = await universign_statut(signature_log.transaction_id)
|
||||
|
||||
return {
|
||||
"doc_id": docId,
|
||||
"statut": statut["statut"],
|
||||
"date_signature": statut.get("date_signature"),
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur statut signature: {e}")
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
@app.get("/signatures", tags=["Signatures"])
|
||||
async def lister_signatures(
|
||||
statut: Optional[StatutSignature] = Query(None),
|
||||
limit: int = Query(100, le=1000),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
query = select(SignatureLog).order_by(SignatureLog.date_envoi.desc())
|
||||
|
||||
if statut:
|
||||
statut_db = StatutSignatureDB[statut.value]
|
||||
query = query.where(SignatureLog.statut == statut_db)
|
||||
|
||||
query = query.limit(limit)
|
||||
result = await session.execute(query)
|
||||
signatures = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": sig.id,
|
||||
"document_id": sig.document_id,
|
||||
"type_document": sig.type_document.value,
|
||||
"transaction_id": sig.transaction_id,
|
||||
"signer_url": sig.signer_url,
|
||||
"email_signataire": sig.email_signataire,
|
||||
"nom_signataire": sig.nom_signataire,
|
||||
"statut": sig.statut.value,
|
||||
"date_envoi": sig.date_envoi.isoformat() if sig.date_envoi else None,
|
||||
"date_signature": (
|
||||
sig.date_signature.isoformat() if sig.date_signature else None
|
||||
),
|
||||
"est_relance": sig.est_relance,
|
||||
"nb_relances": sig.nb_relances or 0,
|
||||
}
|
||||
for sig in signatures
|
||||
]
|
||||
|
||||
|
||||
@app.get("/signatures/{transaction_id}/status", tags=["Signatures"])
|
||||
async def statut_signature_detail(
|
||||
transaction_id: str, session: AsyncSession = Depends(get_session)
|
||||
):
|
||||
query = select(SignatureLog).where(SignatureLog.transaction_id == transaction_id)
|
||||
result = await session.execute(query)
|
||||
signature_log = result.scalar_one_or_none()
|
||||
|
||||
if not signature_log:
|
||||
raise HTTPException(404, f"Transaction {transaction_id} introuvable")
|
||||
|
||||
statut_universign = await universign_statut(transaction_id)
|
||||
|
||||
if statut_universign.get("statut") != "ERREUR":
|
||||
statut_map = {
|
||||
"EN_ATTENTE": StatutSignatureDB.EN_ATTENTE,
|
||||
"ENVOYE": StatutSignatureDB.ENVOYE,
|
||||
"SIGNE": StatutSignatureDB.SIGNE,
|
||||
"REFUSE": StatutSignatureDB.REFUSE,
|
||||
"EXPIRE": StatutSignatureDB.EXPIRE,
|
||||
}
|
||||
|
||||
nouveau_statut = statut_map.get(
|
||||
statut_universign["statut"], StatutSignatureDB.EN_ATTENTE
|
||||
)
|
||||
|
||||
signature_log.statut = nouveau_statut
|
||||
|
||||
if statut_universign.get("date_signature"):
|
||||
signature_log.date_signature = datetime.fromisoformat(
|
||||
statut_universign["date_signature"].replace("Z", "+00:00")
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"transaction_id": transaction_id,
|
||||
"document_id": signature_log.document_id,
|
||||
"statut": signature_log.statut.value,
|
||||
"email_signataire": signature_log.email_signataire,
|
||||
"date_envoi": (
|
||||
signature_log.date_envoi.isoformat() if signature_log.date_envoi else None
|
||||
),
|
||||
"date_signature": (
|
||||
signature_log.date_signature.isoformat()
|
||||
if signature_log.date_signature
|
||||
else None
|
||||
),
|
||||
"signer_url": signature_log.signer_url,
|
||||
}
|
||||
|
||||
|
||||
@app.post("/signatures/refresh-all", tags=["Signatures"])
|
||||
async def rafraichir_statuts_signatures(session: AsyncSession = Depends(get_session)):
|
||||
query = select(SignatureLog).where(
|
||||
SignatureLog.statut.in_(
|
||||
[StatutSignatureDB.EN_ATTENTE, StatutSignatureDB.ENVOYE]
|
||||
)
|
||||
)
|
||||
|
||||
result = await session.execute(query)
|
||||
signatures = result.scalars().all()
|
||||
nb_mises_a_jour = 0
|
||||
|
||||
for sig in signatures:
|
||||
try:
|
||||
statut_universign = await universign_statut(sig.transaction_id)
|
||||
|
||||
if statut_universign.get("statut") != "ERREUR":
|
||||
statut_map = {
|
||||
"SIGNE": StatutSignatureDB.SIGNE,
|
||||
"REFUSE": StatutSignatureDB.REFUSE,
|
||||
"EXPIRE": StatutSignatureDB.EXPIRE,
|
||||
}
|
||||
|
||||
nouveau = statut_map.get(statut_universign["statut"])
|
||||
|
||||
if nouveau and nouveau != sig.statut:
|
||||
sig.statut = nouveau
|
||||
|
||||
if statut_universign.get("date_signature"):
|
||||
sig.date_signature = datetime.fromisoformat(
|
||||
statut_universign["date_signature"].replace("Z", "+00:00")
|
||||
)
|
||||
|
||||
nb_mises_a_jour += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur refresh signature {sig.transaction_id}: {e}")
|
||||
continue
|
||||
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"nb_signatures_verifiees": len(signatures),
|
||||
"nb_mises_a_jour": nb_mises_a_jour,
|
||||
}
|
||||
|
||||
|
||||
class EmailBatch(BaseModel):
|
||||
destinataires: List[EmailStr] = Field(..., min_length=1, max_length=100)
|
||||
sujet: str = Field(..., min_length=1, max_length=500)
|
||||
|
|
@ -1756,14 +1497,19 @@ class TemplatePreview(BaseModel):
|
|||
|
||||
|
||||
@app.get("/templates/emails", response_model=List[TemplateEmail], tags=["Emails"])
|
||||
async def lister_templates():
|
||||
async def lister_templates(
|
||||
user: User = Depends(get_current_user),
|
||||
):
|
||||
return [TemplateEmail(**template) for template in templates_email_db.values()]
|
||||
|
||||
|
||||
@app.get(
|
||||
"/templates/emails/{template_id}", response_model=TemplateEmail, tags=["Emails"]
|
||||
)
|
||||
async def lire_template(template_id: str):
|
||||
async def lire_template(
|
||||
template_id: str,
|
||||
user: User = Depends(get_current_user),
|
||||
):
|
||||
if template_id not in templates_email_db:
|
||||
raise HTTPException(404, f"Template {template_id} introuvable")
|
||||
|
||||
|
|
@ -1771,7 +1517,10 @@ async def lire_template(template_id: str):
|
|||
|
||||
|
||||
@app.post("/templates/emails", response_model=TemplateEmail, tags=["Emails"])
|
||||
async def creer_template(template: TemplateEmail):
|
||||
async def creer_template(
|
||||
template: TemplateEmail,
|
||||
user: User = Depends(get_current_user),
|
||||
):
|
||||
template_id = str(uuid.uuid4())
|
||||
|
||||
templates_email_db[template_id] = {
|
||||
|
|
@ -1790,7 +1539,11 @@ async def creer_template(template: TemplateEmail):
|
|||
@app.put(
|
||||
"/templates/emails/{template_id}", response_model=TemplateEmail, tags=["Emails"]
|
||||
)
|
||||
async def modifier_template(template_id: str, template: TemplateEmail):
|
||||
async def modifier_template(
|
||||
template_id: str,
|
||||
template: TemplateEmail,
|
||||
user: User = Depends(get_current_user),
|
||||
):
|
||||
if template_id not in templates_email_db:
|
||||
raise HTTPException(404, f"Template {template_id} introuvable")
|
||||
|
||||
|
|
@ -1811,7 +1564,10 @@ async def modifier_template(template_id: str, template: TemplateEmail):
|
|||
|
||||
|
||||
@app.delete("/templates/emails/{template_id}", tags=["Emails"])
|
||||
async def supprimer_template(template_id: str):
|
||||
async def supprimer_template(
|
||||
template_id: str,
|
||||
user: User = Depends(get_current_user),
|
||||
):
|
||||
if template_id not in templates_email_db:
|
||||
raise HTTPException(404, f"Template {template_id} introuvable")
|
||||
|
||||
|
|
@ -2641,6 +2397,7 @@ async def lister_utilisateurs_debug(
|
|||
limit: int = Query(100, le=1000),
|
||||
role: Optional[str] = Query(None),
|
||||
verified_only: bool = Query(False),
|
||||
user: User = Depends(get_current_user),
|
||||
):
|
||||
from database import User
|
||||
from sqlalchemy import select
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import Optional
|
|||
import uuid
|
||||
|
||||
from database import get_session, User, RefreshToken, LoginAttempt
|
||||
from core.dependencies import get_current_user
|
||||
from security.auth import (
|
||||
hash_password,
|
||||
verify_password,
|
||||
|
|
@ -19,7 +20,6 @@ from security.auth import (
|
|||
hash_token,
|
||||
)
|
||||
from services.email_service import AuthEmailService
|
||||
from core.dependencies import get_current_user
|
||||
from config.config import settings
|
||||
import logging
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import selectinload
|
||||
from typing import List, Optional
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from core.dependencies import get_current_user
|
||||
from data.data import templates_signature_email
|
||||
from email_queue import email_queue
|
||||
from database import UniversignSignerStatus, UniversignTransactionStatus, get_session
|
||||
|
|
@ -32,7 +32,9 @@ from schemas import (
|
|||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/universign", tags=["Universign"])
|
||||
router = APIRouter(
|
||||
prefix="/universign", tags=["Universign"], dependencies=[Depends(get_current_user)]
|
||||
)
|
||||
|
||||
sync_service = UniversignSyncService(
|
||||
api_url=settings.universign_api_url, api_key=settings.universign_api_key
|
||||
|
|
@ -494,14 +496,11 @@ async def sync_all_transactions(
|
|||
return {"success": True, "stats": stats, "timestamp": datetime.now().isoformat()}
|
||||
|
||||
|
||||
@router.post("/webhook")
|
||||
@router.post("/webhook/")
|
||||
@router.post("/webhook", dependencies=[])
|
||||
@router.post("/webhook/", dependencies=[])
|
||||
async def webhook_universign(
|
||||
request: Request, session: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
CORRECTION : Extraction correcte du transaction_id selon la structure réelle d'Universign
|
||||
"""
|
||||
try:
|
||||
payload = await request.json()
|
||||
|
||||
|
|
@ -1082,159 +1081,6 @@ async def trouver_transactions_inconsistantes(
|
|||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
@router.post("/admin/nettoyer-transactions-erreur", tags=["Admin"])
|
||||
async def nettoyer_transactions_erreur(
|
||||
age_jours: int = Query(
|
||||
7, description="Supprimer les transactions en erreur de plus de X jours"
|
||||
),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
try:
|
||||
date_limite = datetime.now() - timedelta(days=age_jours)
|
||||
|
||||
query = select(UniversignTransaction).where(
|
||||
and_(
|
||||
UniversignTransaction.local_status == LocalDocumentStatus.ERROR,
|
||||
UniversignTransaction.created_at < date_limite,
|
||||
)
|
||||
)
|
||||
|
||||
result = await session.execute(query)
|
||||
transactions = result.scalars().all()
|
||||
|
||||
supprimees = []
|
||||
for tx in transactions:
|
||||
supprimees.append(
|
||||
{
|
||||
"transaction_id": tx.transaction_id,
|
||||
"document_id": tx.sage_document_id,
|
||||
"date_creation": tx.created_at.isoformat(),
|
||||
"erreur": tx.sync_error,
|
||||
}
|
||||
)
|
||||
await session.delete(tx)
|
||||
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"transactions_supprimees": len(supprimees),
|
||||
"age_limite_jours": age_jours,
|
||||
"details": supprimees,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur nettoyage: {e}")
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
@router.get("/debug/webhook-payload/{transaction_id}", tags=["Debug"])
|
||||
async def voir_dernier_webhook(
|
||||
transaction_id: str, session: AsyncSession = Depends(get_session)
|
||||
):
|
||||
try:
|
||||
query = select(UniversignTransaction).where(
|
||||
UniversignTransaction.transaction_id == transaction_id
|
||||
)
|
||||
result = await session.execute(query)
|
||||
tx = result.scalar_one_or_none()
|
||||
|
||||
if not tx:
|
||||
raise HTTPException(404, "Transaction introuvable")
|
||||
|
||||
logs_query = (
|
||||
select(UniversignSyncLog)
|
||||
.where(
|
||||
and_(
|
||||
UniversignSyncLog.transaction_id == tx.id,
|
||||
UniversignSyncLog.sync_type.like("webhook:%"),
|
||||
)
|
||||
)
|
||||
.order_by(UniversignSyncLog.sync_timestamp.desc())
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
logs_result = await session.execute(logs_query)
|
||||
last_webhook_log = logs_result.scalar_one_or_none()
|
||||
|
||||
if not last_webhook_log:
|
||||
return {
|
||||
"transaction_id": transaction_id,
|
||||
"webhook_recu": tx.webhook_received,
|
||||
"dernier_payload": None,
|
||||
"message": "Aucun webhook reçu pour cette transaction",
|
||||
}
|
||||
|
||||
return {
|
||||
"transaction_id": transaction_id,
|
||||
"webhook_recu": tx.webhook_received,
|
||||
"dernier_webhook": {
|
||||
"timestamp": last_webhook_log.sync_timestamp.isoformat(),
|
||||
"type": last_webhook_log.sync_type,
|
||||
"success": last_webhook_log.success,
|
||||
"payload": json.loads(last_webhook_log.changes_detected)
|
||||
if last_webhook_log.changes_detected
|
||||
else None,
|
||||
},
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur debug webhook: {e}")
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
@router.get(
|
||||
"/transactions/{transaction_id}/document/download", tags=["Documents Signés"]
|
||||
)
|
||||
async def telecharger_document_signe(
|
||||
transaction_id: str, session: AsyncSession = Depends(get_session)
|
||||
):
|
||||
try:
|
||||
query = select(UniversignTransaction).where(
|
||||
UniversignTransaction.transaction_id == transaction_id
|
||||
)
|
||||
result = await session.execute(query)
|
||||
transaction = result.scalar_one_or_none()
|
||||
|
||||
if not transaction:
|
||||
raise HTTPException(404, f"Transaction {transaction_id} introuvable")
|
||||
|
||||
if not transaction.signed_document_path:
|
||||
raise HTTPException(
|
||||
404,
|
||||
"Document signé non disponible localement. "
|
||||
"Utilisez POST /admin/download-missing-documents pour le récupérer.",
|
||||
)
|
||||
|
||||
file_path = Path(transaction.signed_document_path)
|
||||
|
||||
if not file_path.exists():
|
||||
logger.warning(f"Fichier perdu : {file_path}")
|
||||
raise HTTPException(
|
||||
404,
|
||||
"Fichier introuvable sur le serveur. "
|
||||
"Utilisez POST /admin/download-missing-documents pour le récupérer.",
|
||||
)
|
||||
|
||||
download_name = (
|
||||
f"{transaction.sage_document_id}_"
|
||||
f"{transaction.sage_document_type.name}_"
|
||||
f"signe.pdf"
|
||||
)
|
||||
|
||||
return FileResponse(
|
||||
path=str(file_path), media_type="application/pdf", filename=download_name
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur téléchargement document : {e}", exc_info=True)
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
@router.get("/transactions/{transaction_id}/document/info", tags=["Documents Signés"])
|
||||
async def info_document_signe(
|
||||
transaction_id: str, session: AsyncSession = Depends(get_session)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import httpx
|
|||
from datetime import datetime
|
||||
from typing import Optional, Tuple, List
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import false, select, true, update, and_
|
||||
from sqlalchemy import false, select, update, and_
|
||||
import logging
|
||||
|
||||
from config.config import settings
|
||||
|
|
|
|||
Loading…
Reference in a new issue