Diff -- /var/www/vhosts/webstls.com/httpdocs/admin_companies.cgi
Diff

/var/www/vhosts/webstls.com/httpdocs/admin_companies.cgi

added on WebSTLs (webstls.com) at 2026-07-01 22:26:16

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