mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
Modifiche varie
This commit is contained in:
@@ -23,12 +23,13 @@ import uuid
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Query, status
|
||||
from fastapi import APIRouter, Query, Request, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.services.audit_service import get_real_ip
|
||||
from app.services.search_service import SearchService
|
||||
|
||||
from app.config import get_settings
|
||||
@@ -38,12 +39,14 @@ from app.dependencies import CurrentUser, DB
|
||||
from app.models.label import Label
|
||||
from app.models.message import Attachment, Message
|
||||
from app.schemas.message import (
|
||||
AttachmentMatchInfo,
|
||||
AttachmentResponse,
|
||||
MessageBulkUpdateRequest,
|
||||
MessageBulkUpdateResponse,
|
||||
MessageListResponse,
|
||||
MessageResponse,
|
||||
MessageUpdateRequest,
|
||||
SearchMatchInfo,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/messages", tags=["Messages"])
|
||||
@@ -330,8 +333,45 @@ async def list_messages(
|
||||
result = await db.execute(q)
|
||||
items = list(result.scalars().all())
|
||||
|
||||
# ── Popola search_match per i risultati di ricerca ────────────────────────
|
||||
if search and items:
|
||||
term_lower = search.lower()
|
||||
msg_ids = [m.id for m in items]
|
||||
term_like = f"%{search}%"
|
||||
|
||||
# Query batch: allegati con extracted_text che matcha il termine
|
||||
att_result = await db.execute(
|
||||
select(Attachment.id, Attachment.message_id, Attachment.filename)
|
||||
.where(
|
||||
Attachment.message_id.in_(msg_ids),
|
||||
Attachment.extracted_text.ilike(term_like),
|
||||
)
|
||||
)
|
||||
# Mappa message_id → lista di AttachmentMatchInfo che matchano
|
||||
att_matches: dict[uuid.UUID, list[AttachmentMatchInfo]] = {}
|
||||
for row in att_result.fetchall():
|
||||
att_id, msg_id, filename = row
|
||||
att_matches.setdefault(msg_id, []).append(
|
||||
AttachmentMatchInfo(id=att_id, filename=filename)
|
||||
)
|
||||
|
||||
message_responses: list[MessageResponse] = []
|
||||
for m in items:
|
||||
resp = MessageResponse.model_validate(m)
|
||||
in_subject = bool(m.subject and term_lower in m.subject.lower())
|
||||
in_body = bool(m.body_text and term_lower in m.body_text.lower())
|
||||
in_attachments = att_matches.get(m.id, [])
|
||||
resp.search_match = SearchMatchInfo(
|
||||
in_subject=in_subject,
|
||||
in_body=in_body,
|
||||
in_attachments=in_attachments,
|
||||
)
|
||||
message_responses.append(resp)
|
||||
else:
|
||||
message_responses = [MessageResponse.model_validate(m) for m in items]
|
||||
|
||||
return MessageListResponse(
|
||||
items=[MessageResponse.model_validate(m) for m in items],
|
||||
items=message_responses,
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
@@ -340,6 +380,7 @@ async def list_messages(
|
||||
|
||||
@router.patch("/bulk", response_model=MessageBulkUpdateResponse)
|
||||
async def bulk_update_messages(
|
||||
request: Request,
|
||||
data: MessageBulkUpdateRequest,
|
||||
current_user: CurrentUser,
|
||||
db: DB,
|
||||
@@ -349,6 +390,8 @@ async def bulk_update_messages(
|
||||
|
||||
Per is_pending_conservation=True o is_conserved=True richiede can_conserve.
|
||||
"""
|
||||
from app.services.audit_service import log_audit
|
||||
|
||||
if not data.ids:
|
||||
return MessageBulkUpdateResponse(updated=0, items=[])
|
||||
|
||||
@@ -415,6 +458,29 @@ async def bulk_update_messages(
|
||||
elif not data.is_conserved:
|
||||
message.conserved_at = None
|
||||
|
||||
# Registra evento audit bulk
|
||||
if messages:
|
||||
changes: dict = {}
|
||||
for field in ("is_read", "is_starred", "is_archived", "is_trashed",
|
||||
"is_pending_conservation", "is_conserved"):
|
||||
v = getattr(data, field, None)
|
||||
if v is not None:
|
||||
changes[field] = v
|
||||
await log_audit(
|
||||
db,
|
||||
"message.bulk_updated",
|
||||
tenant_id=current_user.tenant_id,
|
||||
user_id=current_user.id,
|
||||
resource_type="message",
|
||||
ip_address=get_real_ip(request),
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
payload={
|
||||
"count": len(messages),
|
||||
"message_ids": [str(m.id) for m in messages],
|
||||
"changes": changes,
|
||||
},
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
if messages:
|
||||
@@ -434,17 +500,37 @@ async def bulk_update_messages(
|
||||
|
||||
@router.get("/{message_id}", response_model=MessageResponse)
|
||||
async def get_message(
|
||||
request: Request,
|
||||
message_id: uuid.UUID,
|
||||
current_user: CurrentUser,
|
||||
db: DB,
|
||||
) -> MessageResponse:
|
||||
"""Carica un messaggio per ID."""
|
||||
from app.services.audit_service import log_audit
|
||||
message = await _resolve_message(message_id, current_user, db)
|
||||
await log_audit(
|
||||
db,
|
||||
"message.opened",
|
||||
tenant_id=current_user.tenant_id,
|
||||
user_id=current_user.id,
|
||||
resource_type="message",
|
||||
resource_id=message.id,
|
||||
ip_address=get_real_ip(request),
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
payload={
|
||||
"subject": message.subject,
|
||||
"from_address": message.from_address,
|
||||
"direction": message.direction,
|
||||
"mailbox_id": str(message.mailbox_id),
|
||||
},
|
||||
)
|
||||
await db.commit()
|
||||
return MessageResponse.model_validate(message)
|
||||
|
||||
|
||||
@router.patch("/{message_id}", response_model=MessageResponse)
|
||||
async def update_message(
|
||||
request: Request,
|
||||
message_id: uuid.UUID,
|
||||
data: MessageUpdateRequest,
|
||||
current_user: CurrentUser,
|
||||
@@ -455,6 +541,8 @@ async def update_message(
|
||||
|
||||
Per is_pending_conservation=True o is_conserved=True richiede can_conserve.
|
||||
"""
|
||||
from app.services.audit_service import log_audit
|
||||
|
||||
message = await _resolve_message(message_id, current_user, db)
|
||||
|
||||
# Verifica permesso conservazione se necessario
|
||||
@@ -464,6 +552,19 @@ async def update_message(
|
||||
await perm_svc.require_can_conserve(current_user, message.mailbox_id)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
ip = get_real_ip(request)
|
||||
ua = request.headers.get("user-agent")
|
||||
base_payload = {"subject": message.subject, "mailbox_id": str(message.mailbox_id)}
|
||||
|
||||
# Mappa flag → coppia (action_true, action_false)
|
||||
_FLAG_ACTIONS: dict[str, tuple[str, str]] = {
|
||||
"is_read": ("message.read", "message.unread"),
|
||||
"is_starred": ("message.starred", "message.unstarred"),
|
||||
"is_archived": ("message.archived", "message.unarchived"),
|
||||
"is_trashed": ("message.trashed", "message.restored"),
|
||||
"is_pending_conservation": ("message.pending_conservation","message.conservation_cancelled"),
|
||||
"is_conserved": ("message.conserved", "message.conservation_removed"),
|
||||
}
|
||||
|
||||
if data.is_read is not None:
|
||||
message.is_read = data.is_read
|
||||
@@ -494,6 +595,23 @@ async def update_message(
|
||||
elif not data.is_conserved:
|
||||
message.conserved_at = None
|
||||
|
||||
# Registra un evento di audit per ogni flag modificato
|
||||
for field, (action_true, action_false) in _FLAG_ACTIONS.items():
|
||||
value = getattr(data, field, None)
|
||||
if value is not None:
|
||||
action = action_true if value else action_false
|
||||
await log_audit(
|
||||
db,
|
||||
action,
|
||||
tenant_id=current_user.tenant_id,
|
||||
user_id=current_user.id,
|
||||
resource_type="message",
|
||||
resource_id=message_id,
|
||||
ip_address=ip,
|
||||
user_agent=ua,
|
||||
payload=base_payload,
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
refreshed = await db.execute(
|
||||
select(Message)
|
||||
@@ -524,12 +642,15 @@ async def list_attachments(
|
||||
|
||||
@router.get("/{message_id}/attachments/{attachment_id}/download")
|
||||
async def download_attachment(
|
||||
request: Request,
|
||||
message_id: uuid.UUID,
|
||||
attachment_id: uuid.UUID,
|
||||
current_user: CurrentUser,
|
||||
db: DB,
|
||||
) -> StreamingResponse:
|
||||
"""Scarica un allegato direttamente da MinIO."""
|
||||
from app.services.audit_service import log_audit
|
||||
|
||||
await _resolve_message(message_id, current_user, db)
|
||||
|
||||
result = await db.execute(
|
||||
@@ -542,6 +663,23 @@ async def download_attachment(
|
||||
if not attachment:
|
||||
raise NotFoundError(f"Allegato {attachment_id} non trovato")
|
||||
|
||||
await log_audit(
|
||||
db,
|
||||
"message.attachment_downloaded",
|
||||
tenant_id=current_user.tenant_id,
|
||||
user_id=current_user.id,
|
||||
resource_type="attachment",
|
||||
resource_id=attachment.id,
|
||||
ip_address=get_real_ip(request),
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
payload={
|
||||
"filename": attachment.filename,
|
||||
"message_id": str(message_id),
|
||||
"size_bytes": attachment.size_bytes,
|
||||
},
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
try:
|
||||
from miniopy_async import Minio
|
||||
|
||||
@@ -580,6 +718,7 @@ async def download_attachment(
|
||||
|
||||
@router.get("/{message_id}/download-package")
|
||||
async def download_package(
|
||||
request: Request,
|
||||
message_id: uuid.UUID,
|
||||
current_user: CurrentUser,
|
||||
db: DB,
|
||||
@@ -588,6 +727,7 @@ async def download_package(
|
||||
import io
|
||||
import zipfile as _zipfile
|
||||
|
||||
from app.services.audit_service import log_audit
|
||||
from miniopy_async import Minio
|
||||
|
||||
message = await _resolve_message(message_id, current_user, db)
|
||||
@@ -682,6 +822,23 @@ async def download_package(
|
||||
safe_subject = (message.subject or "pec").replace("/", "_").replace("\\", "_")[:50]
|
||||
zip_filename = f"pec_{safe_subject}.zip"
|
||||
|
||||
await log_audit(
|
||||
db,
|
||||
"message.package_downloaded",
|
||||
tenant_id=current_user.tenant_id,
|
||||
user_id=current_user.id,
|
||||
resource_type="message",
|
||||
resource_id=message.id,
|
||||
ip_address=get_real_ip(request),
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
payload={
|
||||
"subject": message.subject,
|
||||
"zip_filename": zip_filename,
|
||||
"zip_size_bytes": len(zip_bytes),
|
||||
},
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
return StreamingResponse(
|
||||
iter([zip_bytes]),
|
||||
media_type="application/zip",
|
||||
|
||||
Reference in New Issue
Block a user