refactor(#356): DSAR-Fachlogik vollstaendig aus dem Core extrahiert — dsar_collect/dsar_erase in die 5 beteiligten Contracts (contacts, mail, tasks, calendar, kommunikation); core/jobs.py sammelt/loescht nur Core-eigene Daten und iteriert generisch ueber die Plugin-Registry; neue Plugins liefern DSAR-Kategorien ohne Core-Aenderung; Counts-/Category-Keys unveraendert; tasks/contracts.py von Patch-Artefakt bereinigt
Check Cross-Plugin Imports / check (push) Has been cancelled

fixes #356
This commit is contained in:
Agent Zero
2026-08-27 18:09:15 +02:00
parent 092c2d20fb
commit ad5601eb7d
7 changed files with 228 additions and 153 deletions
@@ -50,6 +50,62 @@ class ContactsContract:
"total": results[0],
}
@staticmethod
async def dsar_collect(
db: AsyncSession, tenant_id: Any, user_id: Any
) -> dict[str, Any]:
"""GDPR Art. 15: collect contact data owned by the user.
Owned by the contacts plugin — core/jobs.py calls this generically
via the contract, it must not know contact internals.
"""
contacts = (
await db.execute(
select(Contact).where(
Contact.tenant_id == tenant_id,
Contact.owner_id == user_id,
Contact.deleted_at.is_(None),
)
)
).scalars().all()
return {
"contacts": [
{
"id": str(c.id),
"type": c.type,
"displayname": c.displayname,
"email_1": c.email_1,
"email_2": c.email_2,
}
for c in contacts
]
}
@staticmethod
async def dsar_erase(
db: AsyncSession, tenant_id: Any, user_id: Any
) -> dict[str, int]:
"""GDPR Art. 17: soft-delete contacts owned by the user.
Soft-delete via deleted_at (audit history must remain intact — it is
a business record, not personal data of the subject; retention
policy governs its cleanup).
"""
from datetime import UTC, datetime
contacts = (
await db.execute(
select(Contact).where(
Contact.tenant_id == tenant_id,
Contact.owner_id == user_id,
Contact.deleted_at.is_(None),
)
)
).scalars().all()
for c in contacts:
c.deleted_at = datetime.now(UTC)
return {"contacts_soft_deleted": len(contacts)}
@classmethod
def get_function(cls, name: str):
"""Return a callable exposed by this contract, or None if absent."""