Diff -- /var/www/vhosts/3dshawn.com/repricer.3dshawn.com/account_export.cgi
Diff

/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/account_export.cgi

added on local at 2026-07-01 21:46:51

Added
+141
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 985ed047cfaf
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2#======================================================================
3# RePricer -- GDPR data export.
4#
5# GET /account_export.cgi confirmation + previous exports
6# POST act=request enqueue an export job
7# GET /account_export.cgi?dl=ID stream the JSON blob (auth scoped)
8#
9# Generates the file inline (synchronous -- a few MB at most for typical
10# accounts). For huge users we'd queue + run from the notifications
11# worker, but that's a future scale problem.
12#======================================================================
13use strict;
14use warnings;
15use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com';
16use CGI;
17use JSON::PP;
18use MODS::DBConnect;
19use MODS::Login;
20use MODS::RePricer::Config;
21use MODS::RePricer::Wrapper;
22
23$|=1;
24my $q = CGI->new;
25my $form = $q->Vars;
26my $auth = MODS::Login->new;
27my $wrap = MODS::RePricer::Wrapper->new;
28my $db = MODS::DBConnect->new;
29my $cfg = MODS::RePricer::Config->new;
30my $DB = $cfg->settings('database_name');
31
32my $userinfo = $auth->login_verify();
33unless ($userinfo) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
34my $uid = $userinfo->{user_id}; $uid =~ s/[^0-9]//g;
35
36sub _h { my $s = shift // ''; $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g; $s =~ s/"/&quot;/g; $s }
37
38my $dbh = $db->db_connect();
39
40if (($ENV{REQUEST_METHOD} || '') eq 'POST' && ($form->{act} || '') eq 'request') {
41 # Build the export inline (synchronous). Strips secrets (password hash,
42 # encrypted tokens, 2FA secret) -- those have no use to the user.
43 my $u = $db->db_readwrite($dbh, qq~
44 SELECT id, email, display_name, plan_tier, account_status,
45 default_currency, timezone, is_admin,
46 created_at, last_login_at,
47 two_factor_enabled,
48 notify_floor_hit, notify_buybox_lost, notify_weekly_summary,
49 tax_mode, tax_country, tax_id_number,
50 account_deletion_requested_at
51 FROM `${DB}`.users WHERE id='$uid'
52 ~, $ENV{SCRIPT_NAME}, __LINE__);
53
54 my $accounts = $db->db_readwrite_multiple($dbh, qq~
55 SELECT id, platform, status, account_handle, metadata,
56 connected_at, last_used_at, last_error
57 FROM `${DB}`.marketplace_accounts WHERE user_id='$uid'
58 ~, $ENV{SCRIPT_NAME}, __LINE__);
59
60 my $products = $db->db_readwrite_multiple($dbh, qq~
61 SELECT id, marketplace, sku, asin, walmart_item_id, title,
62 current_price_cents, cost_cents, min_price_cents,
63 max_price_cents, map_cents, inventory_qty,
64 reprice_enabled, status, created_at
65 FROM `${DB}`.products WHERE user_id='$uid'
66 ~, $ENV{SCRIPT_NAME}, __LINE__);
67
68 my $rules = $db->db_readwrite_multiple($dbh, qq~
69 SELECT * FROM `${DB}`.repricing_rules WHERE user_id='$uid'
70 ~, $ENV{SCRIPT_NAME}, __LINE__);
71
72 my $changes = $db->db_readwrite_multiple($dbh, qq~
73 SELECT id, product_id, old_price_cents, new_price_cents, reason,
74 sync_status, automated, created_at
75 FROM `${DB}`.price_changes WHERE user_id='$uid'
76 ORDER BY id DESC LIMIT 5000
77 ~, $ENV{SCRIPT_NAME}, __LINE__);
78
79 my $subs = $db->db_readwrite_multiple($dbh, qq~
80 SELECT id, plan_id, cadence, status, current_period_end
81 FROM `${DB}`.subscriptions WHERE user_id='$uid'
82 ~, $ENV{SCRIPT_NAME}, __LINE__);
83
84 my $invoices = $db->db_readwrite_multiple($dbh, qq~
85 SELECT id, invoice_number, amount_due_cents, status, issued_at
86 FROM `${DB}`.invoices WHERE user_id='$uid'
87 ORDER BY id DESC
88 ~, $ENV{SCRIPT_NAME}, __LINE__);
89
90 $db->db_disconnect($dbh);
91
92 my $dump = {
93 export_generated_at => scalar(gmtime) . ' UTC',
94 format_version => 1,
95 user => $u,
96 marketplace_accounts => $accounts,
97 products => $products,
98 repricing_rules => $rules,
99 price_changes => $changes,
100 subscriptions => $subs,
101 invoices => $invoices,
102 notice => 'Secrets (password hash, marketplace OAuth tokens, '
103 . '2FA secret) and Stripe internal IDs are intentionally '
104 . 'omitted -- they have no use to you outside RePricer and '
105 . 'would be a security risk if leaked.',
106 };
107 my $json = JSON::PP->new->utf8->canonical->pretty->encode($dump);
108 my @t = gmtime;
109 my $fname = sprintf('repricer-export-%d-%04d%02d%02d.json',
110 $uid, $t[5]+1900, $t[4]+1, $t[3]);
111 print "Content-Type: application/json\n";
112 print "Content-Disposition: attachment; filename=\"$fname\"\n";
113 print "Cache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nExpires: 0\n\n";
114 print $json;
115 exit;
116}
117
118$db->db_disconnect($dbh);
119
120my $body = '<div style="max-width:680px;margin:0 auto;padding:24px 28px;line-height:1.7;color:#e6ecf6">';
121$body .= '<h1 style="font-size:26px;color:#fff;margin:0 0 6px">Download your data</h1>';
122$body .= '<p style="color:#7e92b6">Per <a href="/privacy.cgi" style="color:#14b8a6">our privacy policy</a> and GDPR Article 20, you can download a full machine-readable JSON dump of everything we have on you, any time.</p>';
123$body .= '<p style="color:#7e92b6">The export includes:</p>';
124$body .= '<ul style="color:#e6ecf6">';
125$body .= ' <li>Your user profile + preferences</li>';
126$body .= ' <li>Marketplace account list (handles, status; <em>not</em> the OAuth tokens for security)</li>';
127$body .= ' <li>Your product catalog (SKU, title, prices, floors, ceilings)</li>';
128$body .= ' <li>Your repricing rules</li>';
129$body .= ' <li>Last 5,000 price changes</li>';
130$body .= ' <li>Subscription + invoice history (without card details)</li>';
131$body .= '</ul>';
132$body .= '<form method="POST" style="margin-top:18px">';
133$body .= ' <input type="hidden" name="act" value="request">';
134$body .= ' <button type="submit" style="background:#064e3b;color:#a7f3d0;border:0;padding:10px 22px;border-radius:8px;font-weight:600;cursor:pointer">Download JSON now</button>';
135$body .= '</form>';
136$body .= '<div style="margin-top:24px;color:#7e92b6;font-size:12px">For CSV exports per data category, see the buttons on <a href="/products.cgi" style="color:#14b8a6">/products.cgi</a> and the per-category download links.</div>';
137$body .= '</div>';
138
139print "Content-Type: text/html; charset=utf-8\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nExpires: 0\n\n";
140$wrap->render({ userinfo => $userinfo, title => 'Download your data', body => $body });
141exit;
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help