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

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

added on local at 2026-07-01 22:09:35

Added
+222
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to ccc310eef2f8
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 -- Models management page.
4#
5# Lists every model the logged-in user owns, with controls for:
6# - visible on storefront (toggle storefront_listings.visible)
7# - reorder (storefront_listings.sort_order, via move_up/move_down)
8# - feature (promote to sort_order = MIN - 1)
9# - edit (links to /upload_model.cgi?model_id=N -- upload_model.cgi
10# serves the edit form when model_id is set; /edit_model.cgi 302s
11# to the same URL for legacy compatibility)
12#
13# Writes happen via /update_listing.cgi (per-row actions).
14#======================================================================
15use strict;
16use warnings;
17
18use lib '/var/www/vhosts/3dshawn.com/shop.3dshawn.com';
19use CGI;
20use MODS::Template;
21use MODS::DBConnect;
22use MODS::Login;
23use MODS::ShopCart::Config;
24use MODS::ShopCart::Wrapper;
25
26my $q = CGI->new;
27my $form = $q->Vars;
28my $tfile = MODS::Template->new;
29my $db = MODS::DBConnect->new;
30my $auth = MODS::Login->new;
31my $config = MODS::ShopCart::Config->new;
32my $wrap = MODS::ShopCart::Wrapper->new;
33my $DB = $config->settings('database_name');
34
35$| = 1;
36
37my $userinfo = $auth->login_verify();
38if (!$userinfo) {
39 print "Status: 302 Found\nLocation: /login.cgi\n\n";
40 exit;
41}
42require MODS::ShopCart::Permissions;
43MODS::ShopCart::Permissions->new->require_feature($userinfo, 'view_models');
44
45my $uid = $userinfo->{user_id};
46$uid =~ s/[^0-9]//g;
47
48my $dbh = $db->db_connect();
49
50# The user's storefront -- needed to LEFT JOIN listing state. A creator
51# without a storefront yet still sees their models (just no listing
52# controls).
53my $store = $db->db_readwrite($dbh,
54 qq~SELECT id FROM `${DB}`.storefronts WHERE user_id='$uid' ORDER BY id LIMIT 1~,
55 $ENV{SCRIPT_NAME}, __LINE__);
56my $sid = ($store && $store->{id}) ? $store->{id} : 0;
57
58# Filter buckets the user can switch between. URL: /models.cgi?filter=NAME.
59# Each shows a different slice of the user's models.
60my $filter = lc($form->{filter} || 'all');
61$filter =~ s/[^a-z]//g;
62my %VALID_FILTERS = map { $_ => 1 } qw(all drafts published hidden trash);
63$filter = 'all' unless $VALID_FILTERS{$filter};
64
65# Pull every model owned by the user (including removed -- the
66# in-template counts and the "trash" filter need to see them too).
67# LEFT JOIN so models that haven't been listed yet still appear.
68my $sql = qq~
69 SELECT m.id, m.title, m.tagline, m.base_price_cents, m.currency,
70 m.status, m.visibility, m.hero_image_pos,
71 m.category, m.created_at,
72 sl.visible AS listing_visible, sl.sort_order AS listing_sort,
73 COALESCE(NULLIF(m.hero_image_url, ''),
74 (SELECT CONCAT('/img.cgi?m=', mi.id)
75 FROM `${DB}`.model_images mi
76 WHERE mi.model_id = m.id
77 ORDER BY mi.is_primary DESC, mi.sort_order ASC, mi.id ASC
78 LIMIT 1)) AS hero_image_url
79 FROM `${DB}`.models m
80 LEFT JOIN `${DB}`.storefront_listings sl
81 ON sl.model_id = m.id AND sl.storefront_id = '$sid'
82 WHERE m.user_id = '$uid' AND m.purged_at IS NULL
83 ORDER BY
84 CASE WHEN m.status = 'removed' THEN 2
85 WHEN sl.visible = 1 THEN 0
86 ELSE 1 END,
87 sl.sort_order ASC,
88 m.created_at DESC
89 LIMIT 500
90~;
91my @rows = $db->db_readwrite_multiple($dbh, $sql, $ENV{SCRIPT_NAME}, __LINE__);
92
93my @all;
94my %counts = (
95 total => 0, # everything except removed
96 drafts => 0, # status='draft'
97 published => 0, # status='published' AND listing.visible=1
98 hidden => 0, # has listing row AND visible=0
99 trash => 0, # status='removed'
100);
101foreach my $r (@rows) {
102 my $cents = $r->{base_price_cents} || 0;
103 my $price = '$' . sprintf('%.2f', $cents / 100);
104 # No more rotation through the /assets/images/<N>.webp stock-render
105 # placeholders -- they read as "this is your actual model" and
106 # confused users on marketplace-imported drafts. Single clean
107 # "NO COVER YET" SVG fills in until the seller uploads a real image.
108 my $hero = $r->{hero_image_url} || '/assets/no-cover.svg';
109 my $pos = $r->{hero_image_pos} || '50% 50%';
110 my $is_visible = (defined $r->{listing_visible} && $r->{listing_visible}) ? 1 : 0;
111 my $is_listed = defined $r->{listing_visible} ? 1 : 0;
112 my $is_removed = ($r->{status} eq 'removed') ? 1 : 0;
113 my $is_draft = ($r->{status} eq 'draft') ? 1 : 0;
114 my $is_pub_visible = ($r->{status} eq 'published' && $is_visible) ? 1 : 0;
115 my $is_listed_hidden = ($is_listed && !$is_visible && !$is_removed) ? 1 : 0;
116
117 if ($is_removed) {
118 $counts{trash}++;
119 } else {
120 $counts{total}++;
121 $counts{drafts}++ if $is_draft;
122 $counts{published}++ if $is_pub_visible;
123 $counts{hidden}++ if $is_listed_hidden;
124 }
125
126 push @all, {
127 id => $r->{id},
128 title => $r->{title},
129 tagline => $r->{tagline} || '',
130 price => $price,
131 category => $r->{category} || '',
132 status => $r->{status},
133 is_draft => $is_draft,
134 is_published => ($r->{status} eq 'published') ? 1 : 0,
135 is_removed => $is_removed,
136 hero_image => $hero,
137 hero_pos => $pos,
138 is_visible => $is_visible,
139 is_listed => $is_listed,
140 is_pub_visible => $is_pub_visible,
141 is_listed_hidden => $is_listed_hidden,
142 sort_order => defined $r->{listing_sort} ? $r->{listing_sort} : '',
143 status_pill => _status_pill($r->{status}, $is_visible, $is_listed),
144 vis_label => $is_visible ? 'On storefront' : ($is_listed ? 'Hidden' : 'Not listed'),
145 publish_label => ($r->{status} eq 'draft') ? 'Publish' : 'Unpublish',
146 };
147}
148
149# Apply the chosen filter for what to render in the grid.
150my @models;
151foreach my $m (@all) {
152 if ($filter eq 'trash') { push @models, $m if $m->{is_removed}; }
153 elsif ($filter eq 'drafts') { push @models, $m if $m->{is_draft} && !$m->{is_removed}; }
154 elsif ($filter eq 'published') { push @models, $m if $m->{is_pub_visible}; }
155 elsif ($filter eq 'hidden') { push @models, $m if $m->{is_listed_hidden}; }
156 else { push @models, $m unless $m->{is_removed}; }
157}
158
159$db->db_disconnect($dbh);
160
161my $tvars = {
162 user_id => $uid,
163 has_models => scalar(@models) ? 1 : 0,
164 model_count => $counts{total},
165 drafts_count => $counts{drafts},
166 listed_count => $counts{published},
167 hidden_count => $counts{hidden},
168 trash_count => $counts{trash},
169 has_trash => $counts{trash} ? 1 : 0,
170 models => \@models,
171 filter => $filter,
172 is_filter_all => ($filter eq 'all') ? 1 : 0,
173 is_filter_drafts => ($filter eq 'drafts') ? 1 : 0,
174 is_filter_pub => ($filter eq 'published') ? 1 : 0,
175 is_filter_hidden => ($filter eq 'hidden') ? 1 : 0,
176 is_filter_trash => ($filter eq 'trash') ? 1 : 0,
177 filter_label => _filter_label($filter, scalar(@models)),
178};
179
180print "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";
181
182my $body = join('', $tfile->template('shopcart_models.html', $tvars, $userinfo));
183
184$wrap->render({
185 userinfo => $userinfo,
186 page_key => 'models',
187 title => 'Models',
188 body => $body,
189});
190exit;
191
192
193sub _filter_label {
194 my ($f, $n) = @_;
195 return sprintf('%d total', $n) if $f eq 'all';
196 return sprintf('%d drafts', $n) if $f eq 'drafts';
197 return sprintf('%d on storefront', $n) if $f eq 'published';
198 return sprintf('%d hidden', $n) if $f eq 'hidden';
199 return sprintf('%d in trash', $n) if $f eq 'trash';
200 return sprintf('%d', $n);
201}
202
203sub _status_pill {
204 my ($status, $is_visible, $is_listed) = @_;
205 if ($status eq 'draft') {
206 return '<span class="pill" style="background:rgba(245,158,11,0.18);color:#fbbf24">Draft</span>';
207 }
208 if ($status eq 'archived') {
209 return '<span class="pill" style="background:rgba(148,163,184,0.18);color:#94a3b8">Archived</span>';
210 }
211 if ($status eq 'unlisted') {
212 return '<span class="pill" style="background:rgba(59,130,246,0.18);color:#60a5fa">Unlisted</span>';
213 }
214 # published
215 if ($is_visible) {
216 return '<span class="pill success">On storefront</span>';
217 }
218 if ($is_listed) {
219 return '<span class="pill" style="background:rgba(124,58,237,0.18);color:#a78bfa">Hidden</span>';
220 }
221 return '<span class="pill" style="background:rgba(148,163,184,0.18);color:#94a3b8">Not listed</span>';
222}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help