Skip to content

Comments

feat: Implement User Data Export (JSON & HTML)#8

Merged
fortune710 merged 8 commits intofortune710:mainfrom
MaestroDev19:main
Dec 19, 2025
Merged

feat: Implement User Data Export (JSON & HTML)#8
fortune710 merged 8 commits intofortune710:mainfrom
MaestroDev19:main

Conversation

@MaestroDev19
Copy link
Collaborator

@MaestroDev19 MaestroDev19 commented Dec 15, 2025

Summary

Adds functionality for users to export their account data (profile, entries, friendships) via the Settings screen. Users can choose between raw JSON format or a readable HTML format with clickable content links.

Changes

Backend

  • Added GET /user/{user_id}/export endpoint.
  • Implemented data fetching for Profile, Entries, and Friendships.
  • Added support for ?format=html to generate styled HTML output.
  • Fixed synchronous Supabase client blocking by using synchronous route handler.

Frontend (App)

  • Added "Export Data" button to Settings screen.
  • Implemented file download using expo-file-system.
  • Added expo-sharing to functionality to save/share the exported file.
  • Added format selection dialog (JSON/HTML) and loading state.

Summary by CodeRabbit

  • New Features

    • Export Data: users can download their profile, entries, and friendships as JSON or HTML from Settings.
    • Built-in share/save flow to share or store exported files; UI shows exporting state and success/error feedback.
    • Swipe-down gesture in Settings to quickly dismiss the screen.
  • Documentation

    • Added comprehensive README with features, architecture, setup/configuration (including mobile QR testing), and contribution guidelines.

✏️ Tip: You can customize this high-level summary in your review settings.

…ser router to the backend. Debugging Notes

Timeout Issue: Switched backend handler from async def to def to prevent blocking the event loop with synchronous Supabase client calls.
Frontend Deprecation: Updated expo-file-system import to expo-file-system/legacy to support downloadAsync.
User Verification: User confirmed feature works in production environment. Dev environment timing/timeouts may persist due to networking but logic is sound.
…vacy, data management, and authentication actions
@vercel
Copy link

vercel bot commented Dec 15, 2025

@MaestroDev19 is attempting to deploy a commit to the fortune710's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 15, 2025

Warning

Rate limit exceeded

@MaestroDev19 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 4 minutes and 11 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between f917927 and 9d46928.

📒 Files selected for processing (1)
  • backend/routers/user.py (2 hunks)

Walkthrough

Adds a user data export feature: backend endpoint to export a user's profile, entries, and friendships as JSON or HTML; frontend settings UI to request export, save locally, and share; also adds a new README and the expo-sharing dependency.

Changes

Cohort / File(s) Change Summary
Documentation
README.md
New comprehensive project README covering features, architecture, prerequisites, environment variables, backend/frontend setup, contribution workflow, and license.
Backend — User Export
backend/routers/user.py
New ExportFormat enum (json, html) and GET /{user_id}/export endpoint. Authorizes requester, queries Supabase for profile/entries/friendships, sanitizes data (including URL validation), generates JSON or HTML export, returns as downloadable attachment, logs counts, and returns 500 on failure.
Frontend — Settings UI & Dependency
frontend/app/settings/index.tsx, frontend/package.json
Adds export UI and flow: isExporting state, format selection dialog, request to backend export endpoint with auth, save file via expo-file-system, share/reveal via expo-sharing, swipe-to-close gesture, and progress indicator. Adds "expo-sharing": "^14.0.8" to dependencies.

Sequence Diagram

sequenceDiagram
    participant User as User (App)
    participant Settings as Settings UI
    participant API as Backend API
    participant Supabase as Supabase
    participant FS as Device File System
    participant Share as Share Dialog

    User->>Settings: Tap "Export Data"
    Settings->>User: Prompt for format (JSON / HTML)
    User->>Settings: Select format
    Settings->>API: GET /users/{user_id}/export?format={fmt} (with auth token)
    API->>Supabase: Query profile, entries, friendships
    Supabase-->>API: Return data
    alt format == "json"
        API->>API: Assemble JSON payload
    else format == "html"
        API->>API: Render sanitized HTML (validate URLs)
    end
    API-->>Settings: Respond with file attachment (Content-Disposition)
    Settings->>FS: Write file to local URI
    FS-->>Settings: Return file URI
    Settings->>Share: Open share/save dialog (expo-sharing)
    Share-->>User: Present share/save options
    User->>Share: Choose destination
    Share-->>Settings: Return success / error
    Settings-->>User: Show confirmation or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • backend/routers/user.py — authorization checks, Supabase queries, HTML rendering and URL sanitization, response headers and attachment handling, and error logging.
    • frontend/app/settings/index.tsx — auth token usage, async export flow, file write permissions/paths, expo-sharing integration, and gesture interaction.
    • frontend/package.json — ensure expo-sharing is compatible with the project's Expo SDK.

Poem

🐰 I hopped through code with quiet glee,
Collected profiles, entries, tree.
JSON neat or HTML bright,
I save and share by moonlit byte.
— a rabbit, exporting through the night

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Implement User Data Export (JSON & HTML)' accurately summarizes the main change: adding user data export functionality with both JSON and HTML format options.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
backend/routers/user.py (2)

71-76: Validate the format query parameter.

The format parameter accepts any string but only "json" and "html" are handled. Invalid values silently default to JSON, which may confuse users. Consider using an Enum for type safety and explicit validation.

+from enum import Enum
+
+class ExportFormat(str, Enum):
+    json = "json"
+    html = "html"
+
 @router.get("/{user_id}/export")
 def download_user_data(
     user_id: str,
-    format: str = "json",
+    format: ExportFormat = ExportFormat.json,
     current_user = Depends(get_current_user)
 ):

86-102: Add logging for authorization failure.

The authorization check at line 81-82 raises an exception but doesn't log the attempt, unlike the similar check in delete_user (line 28). Consistent logging helps with security auditing.

     if current_user.user.id != user_id:
+        logger.warning(f"Unauthorized export attempt. User {current_user.user.id} tried to export {user_id}")
         raise HTTPException(status_code=403, detail="Not authorized to export this user's data")
frontend/app/settings/index.tsx (1)

125-137: Remove or guard console.log statements for production.

Debug logging with console.log should use the project's logger utility (frontend/lib/logger.ts) for consistent log levels and formatting, or be removed before production.

+import { logger } from '@/lib/logger';

-      console.log(`Exporting ${format} data for user:`, profile.id);
+      logger.info(`Exporting ${format} data for user: ${profile.id}`);

-      console.log('Download result:', result);
+      logger.info('Download result:', result);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00eb004 and 1601c2a.

⛔ Files ignored due to path filters (1)
  • frontend/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • README.md (1 hunks)
  • backend/routers/user.py (2 hunks)
  • frontend/app/settings/index.tsx (4 hunks)
  • frontend/package.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/app/settings/index.tsx (2)
frontend/lib/constants.ts (1)
  • BACKEND_URL (74-74)
frontend/lib/logger.ts (1)
  • error (32-35)
backend/routers/user.py (2)
backend/services/supabase_client.py (1)
  • get_supabase_client (4-9)
backend/utils/auth.py (1)
  • get_current_user (8-34)
🪛 Ruff (0.14.8)
backend/routers/user.py

75-75: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🔇 Additional comments (5)
frontend/package.json (1)

60-60: LGTM!

The expo-sharing dependency addition is appropriate for the new data export feature and the version aligns with other Expo SDK 54 packages in the project.

README.md (1)

1-120: Well-structured project documentation!

The README provides comprehensive setup instructions and clearly documents the architecture, prerequisites, and environment configuration. This will greatly help new contributors onboard quickly.

frontend/app/settings/index.tsx (3)

112-171: Export flow implementation looks solid.

Good practices observed:

  • Authentication validation before making requests
  • Proper loading state management with finally block
  • Graceful fallback when sharing is unavailable
  • Clear error messages for users

300-315: Export Data UI integration is well implemented.

The button correctly:

  • Shows loading state with ActivityIndicator during export
  • Disables interaction while exporting to prevent duplicate requests
  • Provides clear feedback with dynamic text

139-141: The backend export endpoint correctly returns only status 200 for successful exports (both HTML and JSON formats), so the frontend check is appropriate. No changes needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
backend/routers/user.py (3)

125-130: Refactor one-liner for style compliance.

The URL validation logic is correct and secure. However, the one-liner on Line 126 violates PEP 8 style guidelines.

 def validate_url(url: str) -> str:
-    if not url: return ""
+    if not url:
+        return ""
     # Simple scheme check to prevent javascript: or data: exploits
     if url.lower().startswith(('http://', 'https://')):
         return html.escape(url)
     return ""

Based on static analysis hints (Ruff E701).


149-149: Use timezone-aware datetime for exports.

The export timestamps use datetime.now() without timezone information, which can be ambiguous for users in different timezones or when correlating with server logs.

+from datetime import datetime, timezone
+
 # Line 149:
-<div class="meta">Exported on: {html.escape(datetime.now().isoformat())}</div>
+<div class="meta">Exported on: {html.escape(datetime.now(timezone.utc).isoformat())}</div>

 # Line 203:
-"exported_at": datetime.now().isoformat(),
+"exported_at": datetime.now(timezone.utc).isoformat(),

Also applies to: 203-203


92-108: Consider adding pagination or size limits for large exports.

The endpoint fetches all entries and friendships without pagination. Users with thousands of entries could experience slow exports, memory pressure, or request timeouts.

Consider one of these approaches:

  • Add a reasonable row limit (e.g., 10,000 entries) with a note in the export file
  • Implement chunked/streamed export for HTML generation
  • Document expected limits in the API documentation

This is especially important if entries can include large text content or if you expect power users to accumulate significant data over time.

frontend/app/settings/index.tsx (2)

167-168: Use logger.error for consistency.

Line 167 uses console.error while the rest of the export flow uses the logger utility (Line 126, 138). This inconsistency makes log filtering and debugging harder.

-    } catch (error: any) {
-      console.error('❌ Export Data Error:', error);
+    } catch (error: any) {
+      logger.error('Export Data Error', error);
       Alert.alert('Error', error.message || 'Failed to export account data');

72-83: Consider device-relative gesture thresholds.

The gesture uses hard-coded pixel values (absoluteY < 100, absoluteY < 200) that may not work consistently across devices with different screen sizes and pixel densities.

Consider using Dimensions or percentage-based thresholds:

import { Dimensions } from 'react-native';

const { height: screenHeight } = Dimensions.get('window');
const SWIPE_THRESHOLD_PERCENT = 0.15; // 15% of screen height

const swipeDownGesture = Gesture.Pan()
  .onUpdate((event) => {
    if (event.translationY > 0 && event.absoluteY < screenHeight * 0.1) {
      // Handle swipe down animation
    }
  })
  .onEnd((event) => {
    if (event.translationY > 100 && event.velocityY > 500 && 
        event.absoluteY < screenHeight * SWIPE_THRESHOLD_PERCENT) {
      router.back();
    }
  });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1601c2a and 39b312c.

📒 Files selected for processing (2)
  • backend/routers/user.py (2 hunks)
  • frontend/app/settings/index.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/app/settings/index.tsx (2)
frontend/lib/constants.ts (1)
  • BACKEND_URL (74-74)
frontend/lib/logger.ts (2)
  • logger (18-36)
  • error (32-35)
🪛 Ruff (0.14.8)
backend/routers/user.py

80-80: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


126-126: Multiple statements on one line (colon)

(E701)

🔇 Additional comments (5)
backend/routers/user.py (2)

76-88: LGTM — Proper authorization and synchronous handler choice.

The endpoint correctly validates that users can only export their own data. Using a synchronous route handler is appropriate here given the PR description mentions using a synchronous Supabase client to avoid blocking issues.


122-191: XSS protections properly implemented.

The HTML export correctly escapes all user-controlled data and validates URLs to prevent javascript: injection. This addresses the critical security issues raised in previous reviews.

frontend/app/settings/index.tsx (3)

240-240: Good fix — GestureDetector now properly wraps content.

The swipeDownGesture is now correctly integrated with GestureDetector, resolving the dead code issue from the previous review.

Also applies to: 348-348


113-172: Robust export implementation with good error handling.

The performExport function properly validates authentication, handles both format types, provides clear user feedback, and includes proper error handling with loading states.


10-10: The /legacy import path is correct and documented by Expo for v19.0.12. It explicitly imports the legacy API for backward compatibility and is the intended way to access legacy FileSystem methods. No change needed.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/routers/user.py (1)

104-108: Consider documenting the trust assumption for the user_id in the filter.

The string interpolation in .or_(f"user_id.eq.{user_id},friend_id.eq.{user_id}") is safe here because user_id is validated against current_user.user.id at line 86. A brief comment would clarify this trust assumption for future maintainers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39b312c and e5c5430.

📒 Files selected for processing (2)
  • backend/routers/user.py (2 hunks)
  • frontend/app/settings/index.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/app/settings/index.tsx (2)
frontend/lib/constants.ts (1)
  • BACKEND_URL (74-74)
frontend/lib/logger.ts (2)
  • logger (18-36)
  • error (32-35)
🪛 Ruff (0.14.8)
backend/routers/user.py

80-80: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🔇 Additional comments (9)
frontend/app/settings/index.tsx (4)

116-145: LGTM on the export flow implementation.

The export logic properly validates authentication state, handles the download with authorization headers, and includes appropriate error handling. The use of expo-file-system/legacy aligns with current Expo guidance for this API.


147-167: Good fallback handling for sharing availability.

The check for Sharing.isAvailableAsync() with a fallback to show the file path ensures users on platforms without sharing support still get feedback.


305-320: LGTM on export button UI.

The loading state with ActivityIndicator, disabled state, and text change follows the same pattern as the delete account button, maintaining UI consistency.


243-244: Previous dead code issue addressed.

The GestureDetector now properly wraps the content, activating the swipe gesture. However, note the gesture logic concern raised above.

backend/routers/user.py (5)

76-88: Authorization check is correct; static analysis hint is a false positive.

The Ruff B008 warning about Depends() in argument defaults is expected for FastAPI—this is the idiomatic way to declare dependencies. The authorization check properly ensures users can only export their own data.


110-120: LGTM on the remove_none utility.

The recursive filtering produces cleaner export output by eliminating null values from the JSON/HTML representation.


122-199: XSS protections properly implemented.

The validate_url function prevents javascript: and data: URL schemes, and all user-controlled content is passed through html.escape() before HTML insertion. The past XSS concerns are now addressed.


201-216: LGTM on JSON export implementation.

The JSON export correctly structures the data with metadata and uses default=str to handle datetime serialization gracefully.


218-220: Error handling is appropriate.

The generic exception handler with logging and exception chaining provides adequate error reporting without leaking implementation details to clients.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/app/settings/index.tsx (1)

101-178: Export flow well-implemented with proper error handling.

The data export functionality is well-structured:

  • Auth guards prevent unauthorized access
  • Format selection via Alert is user-friendly
  • FileSystem download with authorization header is correct
  • Sharing integration provides good UX
  • Error handling gives clear user feedback

One optional improvement: exported files accumulate in documentDirectory without cleanup. Consider periodically removing old exports or prompting users to delete after sharing.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5c5430 and f917927.

📒 Files selected for processing (2)
  • backend/routers/user.py (2 hunks)
  • frontend/app/settings/index.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
backend/routers/user.py (2)
backend/services/supabase_client.py (1)
  • get_supabase_client (4-9)
backend/utils/auth.py (1)
  • get_current_user (8-34)
frontend/app/settings/index.tsx (2)
frontend/lib/constants.ts (1)
  • BACKEND_URL (74-74)
frontend/lib/logger.ts (2)
  • logger (18-36)
  • error (32-35)
🪛 Ruff (0.14.8)
backend/routers/user.py

80-80: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🔇 Additional comments (7)
backend/routers/user.py (4)

2-12: LGTM! Clean enum and imports.

The new imports and ExportFormat enum are well-structured and support both JSON and HTML export formats cleanly.


76-89: Proper authorization and function design.

The authorization check correctly ensures only the account owner can export their data. The synchronous function signature is intentional (per PR summary) to prevent blocking with the synchronous Supabase client—FastAPI will execute this in a thread pool.

Note: The static analysis hint about Depends in default arguments is a false positive; this is the standard and recommended FastAPI pattern for dependency injection.


124-201: XSS mitigations properly implemented.

The HTML export correctly escapes all user-controlled data and validates URL schemes, addressing the previously flagged XSS vulnerabilities. The validate_url function prevents javascript: injection, and html.escape is consistently applied to all interpolated values.


203-222: LGTM! Clean JSON export and error handling.

The JSON export path is straightforward and secure. Using default=str for JSON serialization handles datetime objects appropriately, and the error handling provides proper feedback without leaking internal details.

frontend/app/settings/index.tsx (3)

1-73: LGTM! Well-organized imports and state.

The new imports, state variables, and refs are properly structured to support the export and swipe-to-close features. The 15% screen height threshold for swipe detection is reasonable.


75-89: Swipe gesture correctly implemented.

The gesture detection now properly tracks the starting Y position in onStart and validates that the swipe began near the top of the screen. This addresses the previous review concern about using absoluteY at gesture end. The combination of start position, translation distance, and velocity creates a natural swipe-to-close experience.


308-323: LGTM! UI integration consistent and accessible.

The export UI is well-integrated into the settings screen:

  • Follows existing styling patterns
  • Loading state prevents concurrent exports
  • Visual feedback with ActivityIndicator
  • Disabled state during export provides good UX

Comment on lines 98 to 110
# 2. Fetch Entries (limit to 10000 to prevent timeout)
logger.info(f"Fetching entries for user_id: {user_id}")
entries_response = supabase.table("entries").select("*").eq("user_id", user_id).limit(10000).execute()
entries_data = entries_response.data
logger.info(f"Found {len(entries_data)} entries")

# 3. Fetch Friendships (both as user and as friend, limit to 10000)
logger.info(f"Fetching friendships for user_id: {user_id}")
# Note: user_id is already validated against current_user.user.id in the auth check above,
# so string interpolation here is safe from injection/tampering.
friendships_response = supabase.table("friendships").select("*").or_(f"user_id.eq.{user_id},friend_id.eq.{user_id}").limit(10000).execute()
friendships_data = friendships_response.data
logger.info(f"Found {len(friendships_data)} friendships")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Document or handle data truncation at 10,000 rows.

The hard limit of 10,000 entries and 10,000 friendships may silently truncate data for power users without warning. For a "data export" feature (especially for GDPR/privacy compliance), users expect complete data.

Consider:

  • Returning a warning in the response if limits were hit
  • Implementing pagination or streaming for large exports
  • At minimum, documenting this limitation prominently in the UI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants