@router.get("/recipient/{cr_id}/history") async def recipient_history(cr_id: int, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)): """Full attempt history for one campaign recipient (modal in Logs page).""" cr = await db.get(CampaignRecipient, cr_id) if not cr: raise HTTPException(status_code=404, detail="Recipient not found") camp = await db.get(Campaign, cr.campaign_id) if not camp or camp.user_id != user.id: raise HTTPException(status_code=404, detail="Recipient not found") attempts = (await db.execute( select(SendAttempt).where(SendAttempt.campaign_recipient_id == cr_id) .order_by(SendAttempt.id.asc()))).scalars().all() events = (await db.execute( select(EmailEvent).where(EmailEvent.campaign_recipient_id == cr_id) .order_by(EmailEvent.id.asc()))).scalars().all() return { "recipient": { "id": cr.id, "email": cr.email, "status": cr.status, "seq": cr.seq, "subject_id": cr.generated_subject_id, "template_id": cr.generated_template_id, "invoice_number": cr.generated_invoice_number, "amount": cr.generated_amount, "message_id": cr.message_id, "last_error": cr.last_error, }, "attempts": [_ser_attempt(a) for a in attempts], "events": [{ "id": e.id, "event_type": e.event_type, "detail": (e.detail or "")[:300], "message_id": e.message_id, "created_at": e.created_at.isoformat() if e.created_at else None, } for e in events], }