+
Export your data
+
+ Download a JSON file with your profile, meshes you own, meshes you
+ joined, invites you've issued, and audit events from your owned
+ meshes. Read-only.
+
+
+ {loading ? "Preparing…" : "Download export"}
+
+ {error &&
{error}
}
+
+ );
+};
diff --git a/packages/api/src/modules/mesh/queries.ts b/packages/api/src/modules/mesh/queries.ts
index ed59610..7232a4f 100644
--- a/packages/api/src/modules/mesh/queries.ts
+++ b/packages/api/src/modules/mesh/queries.ts
@@ -10,7 +10,7 @@ import {
or,
sql,
} from "@turbostarter/db";
-import { invite, mesh, meshMember } from "@turbostarter/db/schema";
+import { auditLog, invite, mesh, meshMember } from "@turbostarter/db/schema";
import { db } from "@turbostarter/db/server";
import type { GetMyMeshesInput } from "../../schema";
@@ -163,6 +163,87 @@ export const getMyMeshById = async ({
};
};
+export const getMyExport = async ({ userId }: { userId: string }) => {
+ const meshesOwned = await db
+ .select({
+ id: mesh.id,
+ name: mesh.name,
+ slug: mesh.slug,
+ visibility: mesh.visibility,
+ transport: mesh.transport,
+ tier: mesh.tier,
+ createdAt: mesh.createdAt,
+ archivedAt: mesh.archivedAt,
+ })
+ .from(mesh)
+ .where(eq(mesh.ownerUserId, userId));
+
+ const memberships = await db
+ .select({
+ meshId: meshMember.meshId,
+ meshName: mesh.name,
+ meshSlug: mesh.slug,
+ memberId: meshMember.id,
+ displayName: meshMember.displayName,
+ role: meshMember.role,
+ joinedAt: meshMember.joinedAt,
+ revokedAt: meshMember.revokedAt,
+ })
+ .from(meshMember)
+ .leftJoin(mesh, eq(meshMember.meshId, mesh.id))
+ .where(eq(meshMember.userId, userId));
+
+ const invitesSent = await db
+ .select({
+ id: invite.id,
+ meshId: invite.meshId,
+ meshSlug: mesh.slug,
+ role: invite.role,
+ maxUses: invite.maxUses,
+ usedCount: invite.usedCount,
+ expiresAt: invite.expiresAt,
+ createdAt: invite.createdAt,
+ revokedAt: invite.revokedAt,
+ })
+ .from(invite)
+ .leftJoin(mesh, eq(invite.meshId, mesh.id))
+ .where(eq(invite.createdBy, userId));
+
+ // Audit events for the user's owned meshes only (privacy: don't leak
+ // events from meshes the user merely joined)
+ const meshIds = meshesOwned.map((m) => m.id);
+ const auditEvents =
+ meshIds.length > 0
+ ? await db
+ .select({
+ id: auditLog.id,
+ meshId: auditLog.meshId,
+ eventType: auditLog.eventType,
+ actorPeerId: auditLog.actorPeerId,
+ targetPeerId: auditLog.targetPeerId,
+ metadata: sql