Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/search.cgi
Diff

/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/search.cgi

added on local at 2026-07-01 13:47:42

Added
+226
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 62b67f29929a
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# AffSoft -- Global search
4#
5# Topbar search input. Scoped to the signed-in user, which may be
6# acting as a merchant (owns affiliate_programs) and/or as an
7# affiliate (owns affiliate_links + receives payouts).
8#
9# Cheap LIKE query across the natural domain tables:
10# - affiliate_programs (slug, name, description, destination_url)
11# - affiliate_links (token, sub_id, label)
12# - affiliate_payouts (external_payout_id, note)
13# Plus a small settings-shortcut hitlist.
14#======================================================================
15use strict;
16use warnings;
17
18use lib '/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com';
19use CGI;
20use MODS::Login;
21use MODS::DBConnect;
22use MODS::AffSoft::Config;
23use MODS::AffSoft::Wrapper;
24
25my $q = CGI->new;
26my $form = $q->Vars;
27my $auth = MODS::Login->new;
28my $wrap = MODS::AffSoft::Wrapper->new;
29my $db = MODS::DBConnect->new;
30my $cfg = MODS::AffSoft::Config->new;
31my $DB = $cfg->settings('database_name');
32
33$|=1;
34my $userinfo = $auth->login_verify();
35if (!$userinfo) {
36 print "Status: 302 Found\nLocation: /login.cgi\n\n";
37 exit;
38}
39my $uid = $userinfo->{user_id};
40$uid =~ s/[^0-9]//g;
41
42my $raw_q = defined $form->{q} ? $form->{q} : '';
43my $clean = $raw_q;
44$clean =~ s/[^A-Za-z0-9._@\s\-]//g;
45$clean =~ s/^\s+|\s+$//g;
46my @terms = grep { length($_) > 0 } split(/[\s\-_]+/, $clean);
47
48sub _h {
49 my $s = shift; $s //= '';
50 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g; $s =~ s/"/&quot;/g;
51 return $s;
52}
53
54sub _term_clause {
55 my (@cols) = @_;
56 return '(1=0)' unless @terms;
57 my @groups;
58 foreach my $col (@cols) {
59 my @parts;
60 foreach my $t (@terms) {
61 my $sql_t = $t; $sql_t =~ s/'/''/g;
62 push @parts, "$col LIKE '%$sql_t%'";
63 }
64 push @groups, '(' . join(' AND ', @parts) . ')';
65 }
66 return '(' . join(' OR ', @groups) . ')';
67}
68
69my $dbh = $db->db_connect();
70
71my (@programs, @links, @payouts);
72if (@terms) {
73 my $prog_where = _term_clause(qw(slug name description destination_url));
74 @programs = $db->db_readwrite_multiple($dbh, qq~
75 SELECT id, slug, name, description, destination_url, status, currency
76 FROM `${DB}`.affiliate_programs
77 WHERE merchant_user_id='$uid' AND $prog_where
78 ORDER BY updated_at DESC LIMIT 25
79 ~, $ENV{SCRIPT_NAME}, __LINE__);
80
81 my $link_where = _term_clause(qw(l.token l.sub_id l.label));
82 @links = $db->db_readwrite_multiple($dbh, qq~
83 SELECT l.id, l.token, l.sub_id, l.label, l.program_id,
84 p.name AS program_name
85 FROM `${DB}`.affiliate_links l
86 LEFT JOIN `${DB}`.affiliate_programs p ON p.id = l.program_id
87 WHERE (l.affiliate_user_id='$uid' OR p.merchant_user_id='$uid')
88 AND $link_where
89 ORDER BY l.created_at DESC LIMIT 25
90 ~, $ENV{SCRIPT_NAME}, __LINE__);
91
92 my $pay_where = _term_clause(qw(external_payout_id note));
93 @payouts = $db->db_readwrite_multiple($dbh, qq~
94 SELECT id, total_cents, currency, status, external_payout_id, note, created_at
95 FROM `${DB}`.affiliate_payouts
96 WHERE (merchant_user_id='$uid' OR affiliate_user_id='$uid')
97 AND $pay_where
98 ORDER BY created_at DESC LIMIT 25
99 ~, $ENV{SCRIPT_NAME}, __LINE__);
100}
101
102my @settings_hits;
103if (length $clean) {
104 my @candidates = (
105 { label => 'Programs', href => '/programs.cgi', keywords => 'program affiliate referral campaign create' },
106 { label => 'Links', href => '/links.cgi', keywords => 'link tracking token url referral sub_id' },
107 { label => 'Payouts', href => '/payouts.cgi', keywords => 'payout payment money paid commission' },
108 { label => 'Account profile', href => '/profile.cgi', keywords => 'account profile name email' },
109 { label => 'Billing & Plan', href => '/billing.cgi', keywords => 'billing subscription invoice card payment plan' },
110 { label => 'Tax forms', href => '/tax_forms.cgi', keywords => 'tax w9 w8 1099 forms' },
111 { label => 'API keys', href => '/api_keys.cgi', keywords => 'api key token webhook integration' },
112 );
113 my $needle = lc $clean;
114 foreach my $c (@candidates) {
115 my $hay = lc($c->{label} . ' ' . $c->{keywords});
116 push @settings_hits, $c if index($hay, $needle) >= 0;
117 }
118}
119
120# Diagnostic counts for empty state
121my $total_programs = 0; my $total_links = 0;
122{
123 my $r = $db->db_readwrite($dbh, qq~SELECT COUNT(*) AS n FROM `${DB}`.affiliate_programs WHERE merchant_user_id='$uid'~, $ENV{SCRIPT_NAME}, __LINE__);
124 $total_programs = ($r && $r->{n}) ? $r->{n} : 0;
125}
126{
127 my $r = $db->db_readwrite($dbh, qq~SELECT COUNT(*) AS n FROM `${DB}`.affiliate_links WHERE affiliate_user_id='$uid'~, $ENV{SCRIPT_NAME}, __LINE__);
128 $total_links = ($r && $r->{n}) ? $r->{n} : 0;
129}
130$db->db_disconnect($dbh);
131
132my $q_h = _h($clean);
133my $body = '';
134
135$body .= qq~
136<div class="page-pad" style="padding:32px">
137 <div style="color:#a78bfa;font-size:12px;text-transform:uppercase;letter-spacing:.1em;font-weight:600;margin-bottom:14px">Search</div>
138 <h1 style="margin:0 0 18px;font-size:32px;letter-spacing:-.02em">~ . (length($clean) ? "Results for &ldquo;$q_h&rdquo;" : "Search") . qq~</h1>
139 <form method="GET" action="/search.cgi" style="margin-bottom:24px">
140 <input type="text" name="q" value="$q_h" placeholder="Search programs, links, payouts..." autocomplete="off" autofocus
141 style="width:100%;max-width:520px;padding:10px 14px;font-size:14px;background:#1a1a1f;color:#fff;border:1px solid #333;border-radius:6px">
142 </form>
143~;
144
145if (!length $clean) {
146 $body .= qq~<div style="color:#888;padding:20px;background:rgba(255,255,255,.03);border-radius:6px">Type a search to find programs, tracking links, or payouts.</div>~;
147} else {
148 my $total = scalar(@programs) + scalar(@links) + scalar(@payouts) + scalar(@settings_hits);
149 if (!$total) {
150 $body .= qq~
151<div style="color:#aaa;padding:24px;background:rgba(255,255,255,.03);border-radius:6px;line-height:1.6">
152 <strong style="color:#fff">No matches for &ldquo;$q_h&rdquo;.</strong><br>
153 You have <strong style="color:#fff">$total_programs</strong> program(s) and <strong style="color:#fff">$total_links</strong> link(s). Try a shorter or different word.
154</div>~;
155 }
156
157 if (@programs) {
158 my $n = scalar @programs;
159 $body .= qq~<div style="margin-bottom:28px"><h2 style="font-size:18px;color:#a78bfa;margin:0 0 12px">Programs ($n)</h2>~;
160 foreach my $p (@programs) {
161 my $name = _h($p->{name} || '(unnamed)');
162 my $slug = _h($p->{slug} || '');
163 my $desc = _h(substr($p->{description} || '', 0, 140));
164 my $url = _h($p->{destination_url} || '');
165 $body .= qq~<a href="/programs.cgi?edit=$p->{id}" style="display:block;padding:14px;margin-bottom:8px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:6px;text-decoration:none;color:#fff">
166 <div style="font-weight:600;font-size:15px">$name</div>
167 <div style="color:#888;font-size:12px;margin-top:4px">/$slug &middot; $url</div>
168 <div style="color:#aaa;font-size:13px;margin-top:6px">$desc</div>
169 </a>~;
170 }
171 $body .= qq~</div>~;
172 }
173
174 if (@links) {
175 my $n = scalar @links;
176 $body .= qq~<div style="margin-bottom:28px"><h2 style="font-size:18px;color:#a78bfa;margin:0 0 12px">Tracking links ($n)</h2>~;
177 foreach my $l (@links) {
178 my $tok = _h($l->{token} || '');
179 my $lbl = _h($l->{label} || '(no label)');
180 my $sub = _h($l->{sub_id} || '');
181 my $prog = _h($l->{program_name} || '');
182 $body .= qq~<a href="/links.cgi?edit=$l->{id}" style="display:block;padding:12px 14px;margin-bottom:6px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:6px;text-decoration:none;color:#fff">
183 <div style="font-weight:600">$lbl</div>
184 <div style="color:#888;font-size:12px;margin-top:3px"><code>$tok</code>~ . ($sub ? " &middot; sub_id: $sub" : '') . qq~ &middot; $prog</div>
185 </a>~;
186 }
187 $body .= qq~</div>~;
188 }
189
190 if (@payouts) {
191 my $n = scalar @payouts;
192 $body .= qq~<div style="margin-bottom:28px"><h2 style="font-size:18px;color:#a78bfa;margin:0 0 12px">Payouts ($n)</h2>~;
193 foreach my $p (@payouts) {
194 my $amt = sprintf('%.2f', ($p->{total_cents} || 0) / 100);
195 my $cur = _h($p->{currency} || 'USD');
196 my $ext = _h($p->{external_payout_id} || ('#' . $p->{id}));
197 my $note = _h(substr($p->{note} || '', 0, 120));
198 my $st = _h($p->{status} || '');
199 $body .= qq~<a href="/payouts.cgi?id=$p->{id}" style="display:block;padding:12px 14px;margin-bottom:6px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:6px;text-decoration:none;color:#fff">
200 <div style="font-weight:600">\$$amt $cur &middot; <span style="color:#888">$st</span></div>
201 <div style="color:#888;font-size:12px;margin-top:3px"><code>$ext</code>~ . ($note ? " &middot; $note" : '') . qq~</div>
202 </a>~;
203 }
204 $body .= qq~</div>~;
205 }
206
207 if (@settings_hits) {
208 my $n = scalar @settings_hits;
209 $body .= qq~<div style="margin-bottom:28px"><h2 style="font-size:18px;color:#a78bfa;margin:0 0 12px">Pages ($n)</h2>~;
210 foreach my $s (@settings_hits) {
211 my $label = _h($s->{label});
212 my $href = _h($s->{href});
213 $body .= qq~<a href="$href" style="display:inline-block;padding:8px 14px;margin:0 8px 8px 0;background:rgba(167,139,250,.1);border:1px solid rgba(167,139,250,.3);border-radius:6px;text-decoration:none;color:#a78bfa;font-size:13px">$label</a>~;
214 }
215 $body .= qq~</div>~;
216 }
217}
218
219$body .= qq~</div>~;
220
221print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n";
222$wrap->render({
223 userinfo => $userinfo,
224 title => length($clean) ? "Search: $q_h" : 'Search',
225 body => $body,
226});
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help