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

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

added on local at 2026-07-01 21:47:18

Added
+227
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to ef0fa018e54a
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 -- Global search
4#
5# Topbar search input. Scoped to the signed-in user.
6#
7# Cheap LIKE query across the seller's natural domain tables:
8# - products (sku, asin, walmart_item_id, upc, title, notes)
9# - repricing_rules (name, strategy)
10# - marketplace_accounts (account_handle, platform)
11# Plus a small settings-shortcut hitlist.
12#======================================================================
13use strict;
14use warnings;
15
16use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com';
17use CGI;
18use MODS::Login;
19use MODS::DBConnect;
20use MODS::RePricer::Config;
21use MODS::RePricer::Wrapper;
22
23my $q = CGI->new;
24my $form = $q->Vars;
25my $auth = MODS::Login->new;
26my $wrap = MODS::RePricer::Wrapper->new;
27my $db = MODS::DBConnect->new;
28my $cfg = MODS::RePricer::Config->new;
29my $DB = $cfg->settings('database_name');
30
31$|=1;
32my $userinfo = $auth->login_verify();
33if (!$userinfo) {
34 print "Status: 302 Found\nLocation: /login.cgi\n\n";
35 exit;
36}
37my $uid = $userinfo->{user_id};
38$uid =~ s/[^0-9]//g;
39
40my $raw_q = defined $form->{q} ? $form->{q} : '';
41my $clean = $raw_q;
42$clean =~ s/[^A-Za-z0-9._@\s\-]//g;
43$clean =~ s/^\s+|\s+$//g;
44my @terms = grep { length($_) > 0 } split(/[\s\-_]+/, $clean);
45
46sub _h {
47 my $s = shift; $s //= '';
48 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g; $s =~ s/"/&quot;/g;
49 return $s;
50}
51
52sub _term_clause {
53 my (@cols) = @_;
54 return '(1=0)' unless @terms;
55 my @groups;
56 foreach my $col (@cols) {
57 my @parts;
58 foreach my $t (@terms) {
59 my $sql_t = $t; $sql_t =~ s/'/''/g;
60 push @parts, "$col LIKE '%$sql_t%'";
61 }
62 push @groups, '(' . join(' AND ', @parts) . ')';
63 }
64 return '(' . join(' OR ', @groups) . ')';
65}
66
67my $dbh = $db->db_connect();
68
69my (@products, @rules, @accounts);
70if (@terms) {
71 my $prod_where = _term_clause(qw(sku asin walmart_item_id upc title notes));
72 @products = $db->db_readwrite_multiple($dbh, qq~
73 SELECT id, sku, asin, walmart_item_id, upc, title, image_url,
74 current_price_cents, currency, marketplace, status, reprice_enabled
75 FROM `${DB}`.products
76 WHERE user_id='$uid' AND $prod_where
77 ORDER BY updated_at DESC LIMIT 25
78 ~, $ENV{SCRIPT_NAME}, __LINE__);
79
80 my $rule_where = _term_clause(qw(name strategy));
81 @rules = $db->db_readwrite_multiple($dbh, qq~
82 SELECT id, name, strategy, scope, is_active, priority
83 FROM `${DB}`.repricing_rules
84 WHERE user_id='$uid' AND $rule_where
85 ORDER BY priority DESC, updated_at DESC LIMIT 25
86 ~, $ENV{SCRIPT_NAME}, __LINE__);
87
88 my $acct_where = _term_clause(qw(account_handle platform));
89 @accounts = $db->db_readwrite_multiple($dbh, qq~
90 SELECT id, platform, account_handle, status, connected_at
91 FROM `${DB}`.marketplace_accounts
92 WHERE user_id='$uid' AND $acct_where
93 ORDER BY connected_at DESC LIMIT 25
94 ~, $ENV{SCRIPT_NAME}, __LINE__);
95}
96
97my @settings_hits;
98if (length $clean) {
99 my @candidates = (
100 { label => 'Products', href => '/products.cgi', keywords => 'product listing sku asin item catalog' },
101 { label => 'Repricing rules', href => '/rules.cgi', keywords => 'rule strategy reprice automation buybox' },
102 { label => 'Marketplaces', href => '/marketplaces.cgi', keywords => 'marketplace amazon walmart ebay account connect' },
103 { label => 'Price changes', href => '/price_changes.cgi',keywords => 'history change log price audit' },
104 { label => 'Account profile', href => '/profile.cgi', keywords => 'account profile name email' },
105 { label => 'Billing & Plan', href => '/billing.cgi', keywords => 'billing subscription invoice card payment plan' },
106 { label => 'API keys', href => '/api_keys.cgi', keywords => 'api key token webhook integration' },
107 );
108 my $needle = lc $clean;
109 foreach my $c (@candidates) {
110 my $hay = lc($c->{label} . ' ' . $c->{keywords});
111 push @settings_hits, $c if index($hay, $needle) >= 0;
112 }
113}
114
115# Diagnostic counts for empty state
116my $total_products = 0; my $total_rules = 0;
117{
118 my $r = $db->db_readwrite($dbh, qq~SELECT COUNT(*) AS n FROM `${DB}`.products WHERE user_id='$uid'~, $ENV{SCRIPT_NAME}, __LINE__);
119 $total_products = ($r && $r->{n}) ? $r->{n} : 0;
120}
121{
122 my $r = $db->db_readwrite($dbh, qq~SELECT COUNT(*) AS n FROM `${DB}`.repricing_rules WHERE user_id='$uid'~, $ENV{SCRIPT_NAME}, __LINE__);
123 $total_rules = ($r && $r->{n}) ? $r->{n} : 0;
124}
125$db->db_disconnect($dbh);
126
127my $q_h = _h($clean);
128my $body = '';
129
130$body .= qq~
131<div class="page-pad" style="padding:32px">
132 <div style="color:#10b981;font-size:12px;text-transform:uppercase;letter-spacing:.1em;font-weight:600;margin-bottom:14px">Search</div>
133 <h1 style="margin:0 0 18px;font-size:32px;letter-spacing:-.02em">~ . (length($clean) ? "Results for &ldquo;$q_h&rdquo;" : "Search") . qq~</h1>
134 <form method="GET" action="/search.cgi" style="margin-bottom:24px">
135 <input type="text" name="q" value="$q_h" placeholder="Search products, rules, marketplaces..." autocomplete="off" autofocus
136 style="width:100%;max-width:520px;padding:10px 14px;font-size:14px;background:#1a1a1f;color:#fff;border:1px solid #333;border-radius:6px">
137 </form>
138~;
139
140if (!length $clean) {
141 $body .= qq~<div style="color:#888;padding:20px;background:rgba(255,255,255,.03);border-radius:6px">Type a search to find products, repricing rules, or marketplace accounts.</div>~;
142} else {
143 my $total = scalar(@products) + scalar(@rules) + scalar(@accounts) + scalar(@settings_hits);
144 if (!$total) {
145 $body .= qq~
146<div style="color:#aaa;padding:24px;background:rgba(255,255,255,.03);border-radius:6px;line-height:1.6">
147 <strong style="color:#fff">No matches for &ldquo;$q_h&rdquo;.</strong><br>
148 You have <strong style="color:#fff">$total_products</strong> product(s) and <strong style="color:#fff">$total_rules</strong> rule(s). Try a shorter or different word.
149</div>~;
150 }
151
152 if (@products) {
153 my $n = scalar @products;
154 $body .= qq~<div style="margin-bottom:28px"><h2 style="font-size:18px;color:#10b981;margin:0 0 12px">Products ($n)</h2>~;
155 foreach my $p (@products) {
156 my $title = _h($p->{title} || '(no title)');
157 my $sku = _h($p->{sku} || '');
158 my $asin = _h($p->{asin} || '');
159 my $price = sprintf('%.2f', ($p->{current_price_cents} || 0) / 100);
160 my $cur = _h($p->{currency} || 'USD');
161 my $mkt = _h($p->{marketplace} || '');
162 my $thumb = _h($p->{image_url} || '');
163 my $thumb_html = $thumb ? qq~<img src="$thumb" style="width:48px;height:48px;object-fit:cover;border-radius:4px;background:#1a1a1f">~
164 : qq~<div style="width:48px;height:48px;background:#1a1a1f;border-radius:4px;display:flex;align-items:center;justify-content:center;color:#555;font-size:10px">no img</div>~;
165 $body .= qq~<a href="/product.cgi?id=$p->{id}" style="display:flex;gap:12px;align-items:center;padding:12px 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 $thumb_html
167 <div style="flex:1;min-width:0">
168 <div style="font-weight:600;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">$title</div>
169 <div style="color:#888;font-size:12px;margin-top:3px">SKU: $sku~ . ($asin ? " &middot; ASIN: $asin" : '') . qq~ &middot; $mkt</div>
170 </div>
171 <div style="text-align:right;color:#10b981;font-weight:600;font-size:14px">\$$price</div>
172 </a>~;
173 }
174 $body .= qq~</div>~;
175 }
176
177 if (@rules) {
178 my $n = scalar @rules;
179 $body .= qq~<div style="margin-bottom:28px"><h2 style="font-size:18px;color:#10b981;margin:0 0 12px">Repricing rules ($n)</h2>~;
180 foreach my $r (@rules) {
181 my $name = _h($r->{name} || '(unnamed)');
182 my $strat = _h($r->{strategy} || '');
183 my $scope = _h($r->{scope} || '');
184 my $active = $r->{is_active} ? '<span style="color:#10b981">active</span>' : '<span style="color:#888">paused</span>';
185 $body .= qq~<a href="/rules.cgi?edit=$r->{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">
186 <div style="font-weight:600">$name</div>
187 <div style="color:#888;font-size:12px;margin-top:3px">$strat &middot; scope: $scope &middot; $active</div>
188 </a>~;
189 }
190 $body .= qq~</div>~;
191 }
192
193 if (@accounts) {
194 my $n = scalar @accounts;
195 $body .= qq~<div style="margin-bottom:28px"><h2 style="font-size:18px;color:#10b981;margin:0 0 12px">Marketplace accounts ($n)</h2>~;
196 foreach my $a (@accounts) {
197 my $plat = _h($a->{platform} || '');
198 my $hand = _h($a->{account_handle} || '(no handle)');
199 my $st = _h($a->{status} || '');
200 $body .= qq~<a href="/marketplaces.cgi?edit=$a->{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">
201 <div style="font-weight:600">$hand</div>
202 <div style="color:#888;font-size:12px;margin-top:3px">$plat &middot; $st</div>
203 </a>~;
204 }
205 $body .= qq~</div>~;
206 }
207
208 if (@settings_hits) {
209 my $n = scalar @settings_hits;
210 $body .= qq~<div style="margin-bottom:28px"><h2 style="font-size:18px;color:#10b981;margin:0 0 12px">Pages ($n)</h2>~;
211 foreach my $s (@settings_hits) {
212 my $label = _h($s->{label});
213 my $href = _h($s->{href});
214 $body .= qq~<a href="$href" style="display:inline-block;padding:8px 14px;margin:0 8px 8px 0;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);border-radius:6px;text-decoration:none;color:#10b981;font-size:13px">$label</a>~;
215 }
216 $body .= qq~</div>~;
217 }
218}
219
220$body .= qq~</div>~;
221
222print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n";
223$wrap->render({
224 userinfo => $userinfo,
225 title => length($clean) ? "Search: $q_h" : 'Search',
226 body => $body,
227});
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help