Skip to content

Account Deletion & Data Export#32

Merged
FLAiRistaken merged 9 commits into
developfrom
jules-14860465044460491026-275ff8f4
May 1, 2026
Merged

Account Deletion & Data Export#32
FLAiRistaken merged 9 commits into
developfrom
jules-14860465044460491026-275ff8f4

Conversation

@FLAiRistaken

@FLAiRistaken FLAiRistaken commented May 1, 2026

Copy link
Copy Markdown
Owner

Implemented account deletion and data export. Added deleteUserAccount and exportUserData to src/lib/db.ts. Connected these functions in src/screens/ProfileScreen.tsx. Updated Build_plan.md tasks.


PR created automatically by Jules for task 14860465044460491026 started by @FLAiRistaken

Summary by CodeRabbit

  • New Features

    • Two-step account deletion that performs complete server-side removal after confirmation.
    • Export of full personal data (profile and logs) as a single CSV with clear section headers.
    • Native sharing of the exported CSV via a temporary file with automatic cleanup.
  • Bug Fixes / UX

    • Actions disabled and busy indicators shown during export/delete.
    • Clear error alerts and cleanup attempts on failures.

Copilot AI review requested due to automatic review settings May 1, 2026 01:13
@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e5c188d9-842a-4e96-aa90-c9a61506e25f

📥 Commits

Reviewing files that changed from the base of the PR and between be92203 and f09706d.

📒 Files selected for processing (1)
  • server/index.js

📝 Walkthrough

Walkthrough

Adds client-side export and delete-account flows in ProfileScreen, two new db exports (exportUserData, deleteUserAccount) that call a proxied server endpoint, a new authenticated proxy route /api/delete-account on the server, an env example entry for the proxy URL, and expo-file-system as a new dependency.

Changes

Cohort / File(s) Summary
Database Utilities
src/lib/db.ts
Adds exportUserData(userId) which concurrently queries logs and profiles, throws on query errors, escapes/quotes CSV fields and returns combined CSV with # PROFILE and # LOGS sections. Adds deleteUserAccount(userId) which reads the active Supabase session access token, requires EXPO_PUBLIC_PROXY_URL/EXPO_PUBLIC_PROXY_SECRET, POSTs { userId, accessToken } to proxy /api/delete-account with a timeout, fails on non-OK/timeouts, and attempts supabase.auth.signOut() without failing on sign-out errors.
Profile Screen (client UI)
src/screens/ProfileScreen.tsx
Derives userId from auth, adds isExporting state and spinner, implements handleExport to call exportUserData, write CSV to a temp file via expo-file-system, call Share.share, and clean up the temp file; implements handleDeleteAccount with two-step confirmation that calls deleteUserAccount; surfaces errors via Alert.alert.
Server endpoint & startup
server/index.js
Adds POST /api/delete-account protected by x-proxy-secret that validates userId and accessToken, verifies token->user match against Supabase, deletes the auth user using the service-role key with timeouts, returns success once auth deletion succeeds, and runs best-effort background cleanup of logs and profiles with retries. Startup now requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY; CORS allows DELETE and Authorization.
Config & deps
.env.example, package.json
Adds EXPO_PUBLIC_PROXY_URL to .env.example. Adds expo-file-system dependency for temporary file operations used during export.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as ProfileScreen
    participant AuthClient as Supabase Auth (client)
    participant Proxy as Proxy Server (/api/delete-account)
    participant DBService as Supabase (service role)

    User->>UI: Tap "Delete account"
    UI->>User: Confirm (step 1)
    User->>UI: Confirm (step 2)
    UI->>AuthClient: getActiveSession() -> access_token, userId
    AuthClient-->>UI: access_token + userId
    UI->>Proxy: POST /api/delete-account { userId, accessToken } with x-proxy-secret
    activate Proxy
    Proxy->>DBService: Verify accessToken matches userId
    DBService-->>Proxy: verification result
    Proxy->>DBService: Delete auth user (service role key)
    DBService-->>Proxy: deletion result
    Proxy->>DBService: Delete logs / profiles (best-effort, retries)
    Proxy-->>UI: 200 OK or error
    UI->>AuthClient: signOut() (errors tolerated)
    AuthClient-->>UI: signOut result
Loading
sequenceDiagram
    actor User
    participant UI as ProfileScreen
    participant DBClient as Supabase (client)
    participant FS as expo-file-system
    participant ShareAPI as Share

    User->>UI: Tap "Export my data"
    UI->>DBClient: fetch profiles (concurrent)
    UI->>DBClient: fetch logs (concurrent)
    DBClient-->>UI: profiles + logs
    UI->>UI: format CSV (# PROFILE, # LOGS, csvEscape)
    UI->>FS: writeFile(tempPath, csv)
    FS-->>UI: temp file URI
    UI->>ShareAPI: Share.share({ url: temp file URI })
    ShareAPI-->>User: system share dialog
    UI->>FS: deleteFile(tempPath) (cleanup after delay)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I quoted each field and bundled each log,
I whispered the secret to a tiny proxy fog,
Two confirmations then the request took flight,
A temp file shared beneath the moonlight,
The rabbit hops off — CSVs tucked tight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Account Deletion & Data Export' directly and concisely summarizes the main changes, which add account deletion and data export functionality across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jules-14860465044460491026-275ff8f4

Review rate limit: 8/10 reviews remaining, refill in 6 minutes and 20 seconds.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/db.ts`:
- Around line 151-175: The CSV builder in profileSection emits many raw cells
(e.g., profile.name, profile.occupation, profile.created_at, log.id,
log.timestamp, log.type, log.category, log.context, log.duration_mins) without
escaping, which breaks CSV when values contain commas/quotes/newlines; add a
small helper (e.g., csvEscape(value)) that converts undefined/null to empty
string, replaces any double-quote with two double-quotes, and wraps the result
in quotes, then apply that helper to every cell in the profileSection array and
inside the logs.map output so all exported cells are properly escaped.
- Around line 104-129: deleteUserAccount currently only signs the user out
(supabase.auth.signOut) and never removes the auth.users record, and it also
ignores signOut errors; replace this client-side-only logic with a server-side
account-deletion flow. Create or call a server endpoint/function (e.g.,
deleteUserAccountAdmin or a serverless route) which uses the Supabase service
role to call auth.admin.deleteUser(userId) and then deletes related rows (logs,
profiles) or relies on DB cascades so the operation is atomic; update the client
deleteUserAccount to call that endpoint and handle/report any errors from the
network call and from signOut (if you still sign out after deletion). Ensure you
reference the current function name deleteUserAccount, the supabase.auth.signOut
usage, and add a server-side method that invokes auth.admin.deleteUser(userId)
with proper error handling and authorization.

In `@src/screens/ProfileScreen.tsx`:
- Around line 43-47: The current share flow in ProfileScreen calls Share.share({
message: csv }) with the CSV string from exportUserData; instead, write the CSV
string to a temporary file and share that file URI via the url param to provide
a downloadable attachment. Replace the Share.share usage so that after calling
exportUserData(userId) you create a temp file (e.g., using Expo FileSystem or
react-native-fs), write the CSV into it, obtain the file:// URI, and call
Share.share({ url: tempFileUri, title: 'Firsthand data export' }) (clean up the
temp file after sharing); update references around exportUserData and
Share.share accordingly.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3926148b-eb51-4dad-9201-e224efeb5814

📥 Commits

Reviewing files that changed from the base of the PR and between 79e0d7d and 9beb626.

⛔ Files ignored due to path filters (1)
  • Build_plan.md is excluded by !**/*.md
📒 Files selected for processing (2)
  • src/lib/db.ts
  • src/screens/ProfileScreen.tsx

Comment thread src/lib/db.ts
Comment thread src/lib/db.ts
Comment thread src/screens/ProfileScreen.tsx
@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 4 file(s) based on 3 unresolved review comments.

A stacked PR containing fixes has been created.

  • Stacked PR: #33
  • Files modified:
  • .env.example
  • server/index.js
  • src/lib/db.ts
  • src/screens/ProfileScreen.tsx

Time taken: 4m 25s

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Fixed 4 file(s) based on 3 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/index.js`:
- Around line 70-78: The code destructures userId and accessToken directly from
req.body which will throw if req.body is null, an array, or a primitive; before
destructuring, validate that req.body is a non-null plain object (e.g., typeof
req.body === 'object' && req.body !== null && !Array.isArray(req.body')), and if
that check fails return res.status(400).json({ error: 'invalid request body' });
then proceed to access userId and accessToken and keep the existing string/trim
checks for userId and accessToken.
- Around line 80-142: The four outbound fetch calls (verifyResponse,
deleteLogsResponse, deleteProfileResponse, deleteUserResponse) can hang
indefinitely; wrap each fetch in a timeout using an AbortController (or a shared
timeout helper) so the request is aborted after a reasonable deadline (e.g.,
5–10s), handle the aborted error path to return a 504 or appropriate error to
the client, and ensure you clean up the controller (clear timers) and log
timeout-specific messages; update the handler's fetch calls to use this
timeout-aware wrapper or inline AbortController logic for the calls to
/auth/v1/user, /rest/v1/logs, /rest/v1/profiles, and
/auth/v1/admin/users/:userId.

In `@src/lib/db.ts`:
- Around line 113-126: Remove use of the client-exposed
EXPO_PUBLIC_PROXY_SECRET: stop reading EXPO_PUBLIC_PROXY_SECRET/proxySecret and
remove the 'x-proxy-secret' header when calling the delete-account endpoint from
deleteAccount (or the function wrapping the fetch call); rely only on the
session access token already sent. Add an AbortController around the fetch to
enforce a 30-second timeout (create controller, pass signal to fetch, and abort
after 30000ms). Finally, change the csvEscape function signature so its input
parameter is typed as unknown instead of any and narrow/check the type inside
the function before operating on it.
- Around line 120-131: The fetch call that posts to
`${proxyUrl}/api/delete-account` can hang indefinitely; add an AbortController
with a suitable timeout (e.g., 5-10s) and pass controller.signal into the fetch
options, set a timer via setTimeout to call controller.abort(), and clear the
timer after fetch settles (in finally) to avoid leaks; ensure you handle
AbortError where this request is made (the code referencing proxyUrl,
proxySecret, userId, session.access_token and the response variable) so the
delete flow fails fast instead of hanging.

In `@src/screens/ProfileScreen.tsx`:
- Around line 41-93: Declare the async handler with an explicit Promise<void>
return type (change "const handleExport = async () => {" to "const handleExport
= async (): Promise<void> =>") and change all catch bindings to typed unknown
(e.g., "catch (error: unknown)" and the two cleanup catches to "catch
(cleanupErr: unknown)"); after that, narrow the unknowns before using them in
logs/alerts (e.g., if (error instanceof Error) console.error('Export error:',
error.message) else console.error('Export error:', String(error))) so the code
remains type-safe while preserving the existing error handling in handleExport,
the setTimeout cleanup callback, and the immediate cleanup block in the main
catch.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d4d2b81a-0c50-4edf-9d51-3094890db012

📥 Commits

Reviewing files that changed from the base of the PR and between 9beb626 and b931394.

⛔ Files ignored due to path filters (2)
  • Build_plan.md is excluded by !**/*.md
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • .env.example
  • package.json
  • server/index.js
  • src/lib/db.ts
  • src/screens/ProfileScreen.tsx

Comment thread server/index.js
Comment thread server/index.js
Comment thread src/lib/db.ts Outdated
Comment on lines +113 to +126
const proxyUrl = process.env.EXPO_PUBLIC_PROXY_URL;
const proxySecret = process.env.EXPO_PUBLIC_PROXY_SECRET;

if (!proxyUrl || !proxySecret) {
throw new Error('Missing proxy configuration');
}

// Call server endpoint to delete user account using service role
const response = await fetch(`${proxyUrl}/api/delete-account`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-proxy-secret': proxySecret,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /dev/shm/repo && head -150 src/lib/db.ts | tail -50

Repository: FLAiRistaken/firsthand

Length of output: 130


🏁 Script executed:

cd /dev/shm/repo && sed -n '104,146p' src/lib/db.ts

Repository: FLAiRistaken/firsthand

Length of output: 130


🏁 Script executed:

cd /dev/shm/repo && sed -n '1,30p' src/lib/db.ts

Repository: FLAiRistaken/firsthand

Length of output: 130


🏁 Script executed:

cat -n src/lib/db.ts | sed -n '100,150p'

Repository: FLAiRistaken/firsthand

Length of output: 2012


🏁 Script executed:

wc -l src/lib/db.ts

Repository: FLAiRistaken/firsthand

Length of output: 84


Remove EXPO_PUBLIC_PROXY_SECRET from client-side authentication.

Anything under EXPO_PUBLIC_* is bundled into the client app, so the header value on lines 123–126 is extractable from the app bundle. The shared-secret check provides no real security here. Rely solely on the session access token (already being sent on line 129) for authentication; move any server-side secrets to a server-only environment.

Also: the fetch call (lines 121–131) lacks an AbortController timeout. Use 30-second timeout per guidelines for src/lib fetch calls. Additionally, csvEscape on line 149 uses value: any — use unknown instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/db.ts` around lines 113 - 126, Remove use of the client-exposed
EXPO_PUBLIC_PROXY_SECRET: stop reading EXPO_PUBLIC_PROXY_SECRET/proxySecret and
remove the 'x-proxy-secret' header when calling the delete-account endpoint from
deleteAccount (or the function wrapping the fetch call); rely only on the
session access token already sent. Add an AbortController around the fetch to
enforce a 30-second timeout (create controller, pass signal to fetch, and abort
after 30000ms). Finally, change the csvEscape function signature so its input
parameter is typed as unknown instead of any and narrow/check the type inside
the function before operating on it.

Comment thread src/lib/db.ts Outdated
Comment thread src/screens/ProfileScreen.tsx Outdated
@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 3 file(s) based on 5 unresolved review comments.

Files modified:

  • server/index.js
  • src/lib/db.ts
  • src/screens/ProfileScreen.tsx

Commit: d42c7b78a30ed1c1bece5c049a1dc8d714adb304

The changes have been pushed to the jules-14860465044460491026-275ff8f4 branch.

Time taken: 3m 13s

Fixed 3 file(s) based on 5 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/index.js`:
- Around line 119-207: Current flow deletes logs and profiles before calling the
auth admin delete, risking irreversible partial deletion; move the auth deletion
to be performed first using the existing deleteUserController/fetch to
/auth/v1/admin/users/${userId} and only on success proceed to delete logs and
profiles (deleteLogsController fetch to /rest/v1/logs?user_id=eq.${userId} and
deleteProfileController fetch to /rest/v1/profiles?id=eq.${userId}). Keep the
same timeout/abort handling and error responses for deleteUserResponse, and for
the subsequent DB deletes implement best-effort behavior (log failures,
optionally retry a few times) rather than returning 500 so the handler does not
leave an active auth account without data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1f0aff63-4279-4859-9270-b674d1f37a27

📥 Commits

Reviewing files that changed from the base of the PR and between b931394 and d42c7b7.

📒 Files selected for processing (3)
  • server/index.js
  • src/lib/db.ts
  • src/screens/ProfileScreen.tsx

Comment thread server/index.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/index.js`:
- Around line 109-110: The code treats any non-2xx from the Supabase
/auth/v1/user call (verifyResponse) as a 401; change the logic so only explicit
auth errors map to 401/403: inspect verifyResponse.status — if 401 return
res.status(401).json(...) and if 403 return res.status(403).json(...); for
rate-limit and server errors (e.g., 429, 5xx) return a 5xx upstream error (e.g.,
res.status(502).json({ error: 'Upstream authentication service error' })) and/or
forward the original status, optionally logging the verifyResponse.status and
body for debugging; update the code around verifyResponse.ok and the
res.status(...) calls to implement this branching.
- Around line 189-193: Return the HTTP success response before running the long
best-effort cleanup loop: call res.json({ success: true }) immediately after
confirming auth deletion, then trigger the two bestEffortDelete calls (logs and
profiles) asynchronously without awaiting them so the client is not blocked and
retries won't run against an expired token; locate the current awaited calls to
bestEffortDelete and the res.json in server/index.js and change the control flow
so res.json is sent first and bestEffortDelete runs in the background (or is
enqueued to a worker).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 31210eab-2124-4bf3-9aff-eb627041e570

📥 Commits

Reviewing files that changed from the base of the PR and between d42c7b7 and be92203.

📒 Files selected for processing (1)
  • server/index.js

Comment thread server/index.js Outdated
Comment thread server/index.js Outdated
@FLAiRistaken FLAiRistaken merged commit 5ffb644 into develop May 1, 2026
4 checks passed
@FLAiRistaken FLAiRistaken deleted the jules-14860465044460491026-275ff8f4 branch May 1, 2026 12:53
@coderabbitai coderabbitai Bot mentioned this pull request May 1, 2026
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