logicspike/docs

Settings

Download My Data — Future Implementation Specification

Overview

The "Download My Data" feature gives users the ability to export a complete copy of their personal data in a portable format (JSON + CSV). This is a GDPR Article 20 requirement ("Right to Data Portability") and a trust-building feature for privacy-conscious users.


Data Exported (User-Level)

Section Contents
Profile Name, email, phone, avatar URL, creation date
Connected Accounts Provider names, connection dates
Workspace Memberships Workspace name, role, join date (for each workspace)
Notification Preferences Current toggle states
Security Events (future) Login history, 2FA events

Not included: Workspace-specific content (e.g., blog posts, ecommerce data) — that is covered by a separate Workspace Data Export feature (future).


Export Format

A ZIP file containing:

data-export-<userId>-<timestamp>.zip
├── profile.json           — Personal profile fields
├── memberships.json       — Workspace memberships + roles
├── notification_prefs.json — Notification toggle states
├── connected_accounts.json — Auth providers
└── README.txt             — Explains the format and what's included

Backend API

POST /manager/profile/export

  • Auth: Must be authenticated (own data only)
  • Rate limit: Max 1 export request per 24 hours per user
  • Process:
    1. Query all user data across tables
    2. Bundle into a ZIP (using a library like jszip or archiver on a Cloudflare Worker with fflate)
    3. Upload ZIP to R2 with a 1-hour pre-signed download URL
    4. Return the pre-signed URL

GET /manager/profile/export/status

  • Check if a pending export is in progress or has a recent download available

Frontend

Location: /settings/profile or /settings/account

A simple section with:

  • "Request Data Export" button
  • On click: shows spinner → "Your export is being prepared"
  • On completion (polling or toast): "Your data export is ready — [Download ZIP]"
  • Download link expires after 1 hour (shown in UI)
  • Cooldown message: "You can request a new export in X hours"

Schema Addition

No new tables required. Add a lightweight user_export_requests table to track cooldowns and export status:

export const userExportRequests = pgTable("user_export_requests", {
    id: text("id").primaryKey(),
    userId: text("user_id").references(() => users.id).notNull(),
    status: text("status").notNull(),        // pending | ready | expired
    downloadUrl: text("download_url"),        // R2 pre-signed URL (set when ready)
    expiresAt: timestamp("expires_at"),       // 1 hour after generation
    requestedAt: timestamp("requested_at").defaultNow().notNull(),
})

Implementation Order

  1. Add userExportRequests to schema + migrate
  2. Build the data aggregation query (joins users, userAuthProviders, memberships, userNotificationPreferences)
  3. Implement ZIP bundling in a Cloudflare Worker (use fflate for WASM-compatible compression)
  4. Upload ZIP to R2 and generate 1-hour presigned download URL
  5. Add POST /profile/export and GET /profile/export/status endpoints
  6. Build the frontend section (ideally in /settings/account under a "Data & Privacy" card)

Privacy & Compliance Notes

  • The export ZIP must be generated server-side — never expose raw DB queries to the client
  • The pre-signed download URL should expire in 1 hour
  • After expiry, the ZIP should be automatically deleted from R2 (use R2 object lifecycle rules)
  • Inform the user clearly in README.txt what data is included and what is not
  • Log the export request in the audit log (when implemented): user.data_export_requested
Settings