fix(api): hide campaign operational internals
This commit is contained in:
@@ -38,6 +38,7 @@ from govoplan_campaign.backend.schemas import (
|
||||
CampaignJobsResponse,
|
||||
CampaignJobsDeltaResponse,
|
||||
CampaignJobDetailResponse,
|
||||
CampaignJobDiagnosticsResponse,
|
||||
CampaignRetryJobsRequest,
|
||||
CampaignSendJobRequest,
|
||||
CampaignSendUnattemptedRequest,
|
||||
@@ -98,6 +99,7 @@ from govoplan_campaign.backend.db.models import (
|
||||
)
|
||||
from govoplan_core.db.session import get_session
|
||||
from govoplan_campaign.backend.reports.campaigns import CampaignReportError, generate_campaign_report, generate_jobs_csv
|
||||
from govoplan_campaign.backend.response_security import public_campaign_payload
|
||||
from govoplan_campaign.backend.reports.emailing import CampaignReportEmailError, send_campaign_report_email
|
||||
from govoplan_campaign.backend.persistence.campaigns import (
|
||||
CampaignPersistenceError,
|
||||
@@ -1812,7 +1814,7 @@ def validate_version(
|
||||
},
|
||||
commit=True,
|
||||
)
|
||||
return result
|
||||
return public_campaign_payload(result)
|
||||
except HTTPException:
|
||||
raise
|
||||
except CampaignPersistenceError as exc:
|
||||
@@ -1847,7 +1849,7 @@ def build_version(
|
||||
details={"write_eml": payload.write_eml if payload else True, "built_count": result.get("built_count")},
|
||||
commit=True,
|
||||
)
|
||||
return result
|
||||
return public_campaign_payload(result)
|
||||
except CampaignPersistenceError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
except Exception as exc:
|
||||
@@ -1904,14 +1906,81 @@ def _job_detail_payload(job: CampaignJob) -> dict[str, object]:
|
||||
return {
|
||||
**_job_summary_payload(job),
|
||||
"message_id_header": job.message_id_header,
|
||||
"eml_local_path": job.eml_local_path,
|
||||
"eml_storage_key": job.eml_storage_key,
|
||||
"issues": job.issues_snapshot or [],
|
||||
"attachments": job.resolved_attachments or [],
|
||||
"attachments": public_campaign_payload(job.resolved_attachments or []),
|
||||
"resolved_recipients": job.resolved_recipients or {},
|
||||
}
|
||||
|
||||
|
||||
def _job_attempts_payload(
|
||||
send_attempts: list[SendAttempt],
|
||||
imap_attempts: list[ImapAppendAttempt],
|
||||
*,
|
||||
include_diagnostics: bool = False,
|
||||
) -> dict[str, list[dict[str, object]]]:
|
||||
smtp_payloads: list[dict[str, object]] = []
|
||||
for attempt in send_attempts:
|
||||
payload: dict[str, object] = {
|
||||
"id": attempt.id,
|
||||
"attempt_number": attempt.attempt_number,
|
||||
"status": attempt.status,
|
||||
"smtp_status_code": attempt.smtp_status_code,
|
||||
"smtp_response": attempt.smtp_response,
|
||||
"error_type": attempt.error_type,
|
||||
"error_message": attempt.error_message,
|
||||
"started_at": attempt.started_at,
|
||||
"finished_at": attempt.finished_at,
|
||||
}
|
||||
if include_diagnostics:
|
||||
payload["claim_token"] = attempt.claim_token
|
||||
smtp_payloads.append(payload)
|
||||
|
||||
imap_payloads: list[dict[str, object]] = []
|
||||
for attempt in imap_attempts:
|
||||
payload = {
|
||||
"id": attempt.id,
|
||||
"attempt_number": attempt.attempt_number,
|
||||
"status": attempt.status,
|
||||
"folder": attempt.folder,
|
||||
"error_message": attempt.error_message,
|
||||
"created_at": attempt.created_at,
|
||||
"updated_at": attempt.updated_at,
|
||||
}
|
||||
if include_diagnostics:
|
||||
payload["claim_token"] = attempt.claim_token
|
||||
imap_payloads.append(payload)
|
||||
return {"smtp": smtp_payloads, "imap": imap_payloads}
|
||||
|
||||
|
||||
def _job_diagnostics_payload(
|
||||
job: CampaignJob,
|
||||
send_attempts: list[SendAttempt],
|
||||
imap_attempts: list[ImapAppendAttempt],
|
||||
) -> CampaignJobDiagnosticsResponse:
|
||||
return CampaignJobDiagnosticsResponse(
|
||||
job_id=job.id,
|
||||
campaign_id=job.campaign_id,
|
||||
campaign_version_id=job.campaign_version_id,
|
||||
storage={
|
||||
"eml_local_path": job.eml_local_path,
|
||||
"eml_storage_key": job.eml_storage_key,
|
||||
"eml_size_bytes": job.eml_size_bytes,
|
||||
"eml_sha256": job.eml_sha256,
|
||||
},
|
||||
worker_claim={
|
||||
"claim_token": job.claim_token,
|
||||
"claimed_at": job.claimed_at,
|
||||
"smtp_started_at": job.smtp_started_at,
|
||||
"outcome_unknown_at": job.outcome_unknown_at,
|
||||
},
|
||||
attempts=_job_attempts_payload(
|
||||
send_attempts,
|
||||
imap_attempts,
|
||||
include_diagnostics=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _review_metadata(
|
||||
session: Session,
|
||||
version: CampaignVersion | None,
|
||||
@@ -2462,38 +2531,42 @@ def get_job_detail(
|
||||
)
|
||||
return CampaignJobDetailResponse(
|
||||
job=_job_detail_payload(job),
|
||||
attempts={
|
||||
"smtp": [
|
||||
{
|
||||
"id": attempt.id,
|
||||
"attempt_number": attempt.attempt_number,
|
||||
"status": attempt.status,
|
||||
"claim_token": attempt.claim_token,
|
||||
"smtp_status_code": attempt.smtp_status_code,
|
||||
"smtp_response": attempt.smtp_response,
|
||||
"error_type": attempt.error_type,
|
||||
"error_message": attempt.error_message,
|
||||
"started_at": attempt.started_at,
|
||||
"finished_at": attempt.finished_at,
|
||||
}
|
||||
for attempt in send_attempts
|
||||
],
|
||||
"imap": [
|
||||
{
|
||||
"id": attempt.id,
|
||||
"attempt_number": attempt.attempt_number,
|
||||
"status": attempt.status,
|
||||
"folder": attempt.folder,
|
||||
"error_message": attempt.error_message,
|
||||
"created_at": attempt.created_at,
|
||||
"updated_at": attempt.updated_at,
|
||||
}
|
||||
for attempt in imap_attempts
|
||||
],
|
||||
},
|
||||
attempts=_job_attempts_payload(send_attempts, imap_attempts),
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{campaign_id}/jobs/{job_id}/diagnostics",
|
||||
response_model=CampaignJobDiagnosticsResponse,
|
||||
)
|
||||
def get_job_diagnostics(
|
||||
campaign_id: str,
|
||||
job_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(require_scope("campaigns:diagnostic:read")),
|
||||
):
|
||||
"""Return infrastructure details only to campaign operators/admins."""
|
||||
|
||||
_get_campaign_for_principal(session, campaign_id, principal)
|
||||
campaign = _get_campaign_for_tenant(session, campaign_id, principal.tenant_id)
|
||||
job = session.get(CampaignJob, job_id)
|
||||
if not job or job.campaign_id != campaign.id or job.tenant_id != principal.tenant_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Campaign job not found")
|
||||
send_attempts = (
|
||||
session.query(SendAttempt)
|
||||
.filter(SendAttempt.job_id == job.id)
|
||||
.order_by(SendAttempt.attempt_number.asc())
|
||||
.all()
|
||||
)
|
||||
imap_attempts = (
|
||||
session.query(ImapAppendAttempt)
|
||||
.filter(ImapAppendAttempt.job_id == job.id)
|
||||
.order_by(ImapAppendAttempt.attempt_number.asc())
|
||||
.all()
|
||||
)
|
||||
return _job_diagnostics_payload(job, send_attempts, imap_attempts)
|
||||
|
||||
|
||||
@router.get("/{campaign_id}/summary")
|
||||
def campaign_summary(
|
||||
campaign_id: str,
|
||||
|
||||
Reference in New Issue
Block a user