From 89a932cdd5dfb05c5e36060684964fd2c563f1df Mon Sep 17 00:00:00 2001 From: Ingo Wagner Date: Wed, 23 Sep 2026 17:42:36 +0000 Subject: [PATCH] =?UTF-8?q?feat(api):=20add=20/api/data/export=20endpoint?= =?UTF-8?q?=20for=20user=20data=20portability=20feat(web):=20add=20DataTru?= =?UTF-8?q?stPanel=20to=20Shell=20sidebar=20showing=20local=20data=20statu?= =?UTF-8?q?s=20=20=20-=203=20trust=20indicators:=20verschl=C3=BCsselt,=20l?= =?UTF-8?q?okal=20gespeichert,=20kein=20Tracking=20=20=20-=20Responsive:?= =?UTF-8?q?=20hidden=20on=20small=20screens,=20visible=20on=20xl+=20=20=20?= =?UTF-8?q?-=20Green=20checkmark=20with=20detail=20text=20for=20each=20tru?= =?UTF-8?q?st=20point?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/app.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index 46f448a..42e86ee 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -831,6 +831,53 @@ export async function createApp( })(); } + + // Data export endpoint — user downloads all their data as JSON + app.get("/api/data/export", async (c) => { + const session = await auth.api.getSession({ headers: sessionHeaders(c.req.raw) }); + if (!session?.user) return c.json({ error: "Unauthorized" }, 401); + + const userId = session.user.id; + + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { id: true, name: true, email: true, emailVerified: true, createdAt: true, updatedAt: true }, + }); + + const bots = await prisma.bot.findMany({ + where: { userId }, + select: { id: true, name: true, createdAt: true, archivedAt: true }, + }); + + const botIds = bots.map((b) => b.id); + const runs = await prisma.run.findMany({ + where: { botId: { in: botIds } }, + select: { id: true, status: true, createdAt: true, updatedAt: true }, + }); + + const consents = await prisma.aiDataConsent.findMany({ + where: { userId }, + select: { recipientKey: true, version: true, grantedAt: true }, + }); + + const exportData = { + exportedAt: new Date().toISOString(), + user, + bots, + runs, + aiDataConsents: consents, + note: "Alle Daten werden lokal auf Ihrem Server gespeichert. Keine externen Dienste.", + }; + + const json = JSON.stringify(exportData, null, 2); + const filename = `evo-data-export-${new Date().toISOString().split("T")[0]}.json`; + + return c.newResponse(json, 200, { + "Content-Type": "application/json", + "Content-Disposition": `attachment; filename="${filename}"`, + }); + }); + app.get("/health", (c) => c.json({ ok: true,