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

O Operator
Diff

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

added on local at 2026-07-11 18:31:39

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