Diff -- /var/www/vhosts/3dshawn.com/shop.3dshawn.com/admin_companies.cgi

O Operator
Diff

/var/www/vhosts/3dshawn.com/shop.3dshawn.com/admin_companies.cgi

added on local at 2026-07-11 18:36:18

Added
+221
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to ae2a86ee1b5d
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# ShopCart - SUPER-ADMIN: list customer storefronts/workspaces.
4# Rich columns mirroring TaskForge admin_companies design.
5#
6# Consolidates the former /admin_company.cgi (per-storefront detail
7# page) via param dispatch on ?id=<int>. When ?id=<int> is present,
8# this file renders the drill-down; otherwise it renders the list.
9#======================================================================
10use strict; use warnings;
11use lib '/var/www/vhosts/3dshawn.com/shop.3dshawn.com';
12use CGI;
13use MODS::Template;
14use MODS::DBConnect;
15use MODS::Login;
16use MODS::ShopCart::Config;
17use MODS::ShopCart::Wrapper;
18use MODS::ShopCart::Admin;
19
20my $q = CGI->new;
21my $form = $q->Vars;
22my $auth = MODS::Login->new;
23my $wrap = MODS::ShopCart::Wrapper->new;
24my $tfile = MODS::Template->new;
25my $db = MODS::DBConnect->new;
26my $cfg = MODS::ShopCart::Config->new;
27my $admin = MODS::ShopCart::Admin->new;
28my $DB = $cfg->settings('database_name');
29
30my $userinfo = $auth->login_verify();
31if (!$userinfo) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
32$admin->require_admin($userinfo);
33require MODS::ShopCart::Permissions;
34MODS::ShopCart::Permissions->new->require_feature($userinfo, 'admin_view_users');
35
36# ---- Param dispatch: ?id=<int> triggers detail view ------------------
37my $sid_param = $form->{id} || '';
38$sid_param =~ s/[^0-9]//g;
39if ($sid_param) {
40 _co_render_detail($sid_param);
41 exit;
42}
43
44_co_render_list();
45exit;
46
47#======================================================================
48# List view (formerly admin_companies.cgi)
49#======================================================================
50sub _co_render_list {
51 my $sq = $form->{q} || '';
52 $sq =~ s/[^A-Za-z0-9_\-\.\s\@]//g;
53
54 my $dbh = $db->db_connect();
55
56 my $where = '1=1';
57 $where .= " AND (s.name LIKE '%$sq%' OR s.subdomain LIKE '%$sq%' OR u.email LIKE '%$sq%')" if $sq;
58
59 my @rows = $db->db_readwrite_multiple($dbh, qq~
60 SELECT s.id, s.name, s.subdomain, s.status, s.created_at,
61 UNIX_TIMESTAMP(s.created_at) AS created_at_epoch,
62 s.user_id, u.email AS owner_email, u.display_name AS owner_name,
63 u.plan_tier AS owner_plan, u.account_status AS owner_status
64 FROM `${DB}`.storefronts s
65 LEFT JOIN `${DB}`.users u ON u.id = s.user_id
66 WHERE $where
67 ORDER BY s.created_at DESC
68 LIMIT 500
69 ~, $ENV{SCRIPT_NAME}, __LINE__);
70
71 # Color palette for the storefront chip (deterministic per id)
72 my @palette = ('#5aa9ff','#34d399','#fbbf24','#a78bfa','#f87171','#22c55e','#06b6d4','#ec4899','#f59e0b');
73
74 foreach my $r (@rows) {
75 $r->{name} = _co_h($r->{name} || '(unnamed)');
76 $r->{owner_email} = _co_h($r->{owner_email} || '');
77 $r->{owner_name} = _co_h($r->{owner_name} || $r->{owner_email});
78 $r->{subdomain} = _co_h($r->{subdomain});
79 $r->{status} = _co_h($r->{status});
80 my $_cr = $r->{created_at}; my $_ce = $r->{created_at_epoch};
81 $r->{created_at} = ($_cr && $_ce)
82 ? qq~<span class="ts" data-ts="$_ce" data-fmt="datetime">$_cr</span>~
83 : '-';
84 $r->{plan_tier} = ucfirst($r->{owner_plan} || 'free');
85 $r->{brand_color} = $palette[ ($r->{id} || 0) % scalar(@palette) ];
86 $r->{users_n} = 1; # one storefront per user on these platforms
87 $r->{trial_ends_at} = '-';
88 }
89
90 $db->db_disconnect($dbh);
91
92 my $tvars = {
93 rows => \@rows,
94 has_rows => scalar(@rows) ? 1 : 0,
95 row_count => scalar(@rows),
96 q => _co_h($sq),
97 };
98
99 print "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";
100 my $body = join('', $tfile->template('shopcart_admin_companies.html', $tvars, $userinfo));
101 $wrap->render({
102 userinfo => $userinfo,
103 page_key => 'admin_companies',
104 title => 'All Companies',
105 body => $body,
106 });
107 return;
108}
109
110#======================================================================
111# Detail view (formerly admin_company.cgi)
112#======================================================================
113sub _co_render_detail {
114 my ($sid) = @_;
115 my $dbh = $db->db_connect();
116
117 my $sf = $db->db_readwrite($dbh, qq~
118 SELECT s.*, u.email AS owner_email, u.display_name AS owner_name,
119 u.id AS owner_user_id, u.plan_tier AS owner_plan,
120 u.account_status AS owner_status, u.last_login_at AS owner_last_login,
121 u.created_at AS owner_created_at, u.avatar_color AS owner_avatar,
122 UNIX_TIMESTAMP(s.created_at) AS sf_created_at_epoch,
123 UNIX_TIMESTAMP(u.last_login_at) AS owner_last_login_epoch,
124 UNIX_TIMESTAMP(u.created_at) AS owner_created_at_epoch
125 FROM `${DB}`.storefronts s
126 LEFT JOIN `${DB}`.users u ON u.id = s.user_id
127 WHERE s.id='$sid' LIMIT 1
128 ~, $ENV{SCRIPT_NAME}, __LINE__);
129
130 unless ($sf && $sf->{id}) {
131 $db->db_disconnect($dbh);
132 print "Status: 302 Found\nLocation: /admin_companies.cgi\n\n";
133 return;
134 }
135
136 # Orders summary (if orders table exists)
137 my ($orders_total, $orders_30, $orders_count, @recent_orders) = (0,0,0);
138 my $tbl = $db->db_readwrite($dbh, qq~SHOW TABLES LIKE 'orders'~, $ENV{SCRIPT_NAME}, __LINE__);
139 if ($tbl && %$tbl) {
140 my $all = $db->db_readwrite($dbh, qq~
141 SELECT COALESCE(SUM(total_amount_cents),0) AS total, COUNT(*) AS n
142 FROM `${DB}`.orders WHERE storefront_id='$sid' AND status='paid'
143 ~, $ENV{SCRIPT_NAME}, __LINE__);
144 $orders_total = $all && $all->{total} ? $all->{total} + 0 : 0;
145 $orders_count = $all && $all->{n} ? $all->{n} + 0 : 0;
146 my $r30 = $db->db_readwrite($dbh, qq~
147 SELECT COALESCE(SUM(total_amount_cents),0) AS total
148 FROM `${DB}`.orders WHERE storefront_id='$sid' AND status='paid'
149 AND created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
150 ~, $ENV{SCRIPT_NAME}, __LINE__);
151 $orders_30 = $r30 && $r30->{total} ? $r30->{total} + 0 : 0;
152 @recent_orders = $db->db_readwrite_multiple($dbh, qq~
153 SELECT id, buyer_email, total_amount_cents, status, created_at,
154 UNIX_TIMESTAMP(created_at) AS created_at_epoch
155 FROM `${DB}`.orders WHERE storefront_id='$sid'
156 ORDER BY created_at DESC LIMIT 10
157 ~, $ENV{SCRIPT_NAME}, __LINE__);
158 foreach my $o (@recent_orders) {
159 $o->{total_disp} = sprintf('$%.2f', ($o->{total_amount_cents}||0)/100);
160 $o->{buyer_email} = _co_h($o->{buyer_email});
161 $o->{status} = _co_h($o->{status});
162 my $_or = $o->{created_at}; my $_oe = $o->{created_at_epoch};
163 $o->{created_at} = ($_or && $_oe)
164 ? qq~<span class="ts" data-ts="$_oe" data-fmt="datetime">$_or</span>~
165 : '-';
166 }
167 }
168
169 $db->db_disconnect($dbh);
170
171 my $tvars = {
172 sid => $sf->{id},
173 sf_name => _co_h($sf->{name} || '(unnamed)'),
174 sf_subdomain => _co_h($sf->{subdomain} || ''),
175 sf_status => _co_h($sf->{status} || 'draft'),
176 sf_custom_domain => _co_h($sf->{custom_domain} || ''),
177 sf_created_at => ($sf->{created_at} && $sf->{sf_created_at_epoch})
178 ? qq~<span class="ts" data-ts="$sf->{sf_created_at_epoch}" data-fmt="datetime">$sf->{created_at}</span>~
179 : '-',
180 sf_ssl_status => _co_h($sf->{ssl_status} || 'none'),
181
182 owner_user_id => $sf->{owner_user_id} || 0,
183 owner_email => _co_h($sf->{owner_email} || ''),
184 owner_name => _co_h($sf->{owner_name} || $sf->{owner_email} || ''),
185 owner_plan => ucfirst($sf->{owner_plan} || 'free'),
186 owner_status => _co_h($sf->{owner_status} || ''),
187 owner_last_login => ($sf->{owner_last_login} && $sf->{owner_last_login_epoch})
188 ? qq~<span class="ts" data-ts="$sf->{owner_last_login_epoch}" data-fmt="datetime">$sf->{owner_last_login}</span>~
189 : 'never',
190 owner_created_at => ($sf->{owner_created_at} && $sf->{owner_created_at_epoch})
191 ? qq~<span class="ts" data-ts="$sf->{owner_created_at_epoch}" data-fmt="datetime">$sf->{owner_created_at}</span>~
192 : '-',
193 owner_avatar => $sf->{owner_avatar} || '#5aa9ff',
194
195 orders_count => $orders_count,
196 orders_total_disp => sprintf('%.2f', $orders_total/100),
197 orders_30_disp => sprintf('%.2f', $orders_30/100),
198 recent_orders => \@recent_orders,
199 has_orders => scalar(@recent_orders) ? 1 : 0,
200 };
201
202 print "Content-Type: text/html; charset=utf-8\nCache-Control: no-store\n\n";
203 my $body = join('', $tfile->template('shopcart_admin_company.html', $tvars, $userinfo));
204 $wrap->render({
205 userinfo => $userinfo,
206 page_key => 'admin_companies',
207 title => 'Admin - ' . ($sf->{name} || 'Storefront'),
208 body => $body,
209 });
210 return;
211}
212
213sub _co_h {
214 my $s = shift;
215 return '' unless defined $s;
216 $s =~ s/&/&amp;/g;
217 $s =~ s/</&lt;/g;
218 $s =~ s/>/&gt;/g;
219 $s =~ s/"/&quot;/g;
220 return $s;
221}