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

/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/_attic/bundles.cgi

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

Added
+239
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 1e481e5b82ad
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 -- seller-side bundle builder.
4#
5# GET /bundles.cgi -> list bundles + new-form
6# GET /bundles.cgi?id=N -> edit one bundle
7# POST /bundles.cgi (_act=...) -> create / update / delete /
8# set_items / publish / archive
9#======================================================================
10use strict;
11use warnings;
12
13use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com';
14use CGI;
15use MODS::Template;
16use MODS::DBConnect;
17use MODS::Login;
18use MODS::RePricer::Config;
19use MODS::RePricer::Wrapper;
20use MODS::RePricer::Permissions;
21use MODS::RePricer::Bundles;
22
23$| = 1;
24
25my $q = CGI->new;
26my $form = $q->Vars;
27my $auth = MODS::Login->new;
28my $tfile = MODS::Template->new;
29my $db = MODS::DBConnect->new;
30my $cfg = MODS::RePricer::Config->new;
31my $wrap = MODS::RePricer::Wrapper->new;
32my $perm = MODS::RePricer::Permissions->new;
33my $bun = MODS::RePricer::Bundles->new;
34my $DB = $cfg->settings('database_name');
35
36my $userinfo = $auth->login_verify();
37unless ($userinfo) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
38$perm->require_feature($userinfo, 'edit_models');
39
40my $uid = $userinfo->{user_id}; $uid =~ s/[^0-9]//g;
41my $owner_uid = $userinfo->{owner_user_id} || $uid;
42$owner_uid =~ s/[^0-9]//g;
43
44my $dbh = $db->db_connect();
45
46unless ($bun->schema_ready($db, $dbh, $DB)) {
47 $db->db_disconnect($dbh);
48 print "Content-Type: text/html; charset=utf-8\n\n";
49 $wrap->render({
50 userinfo => $userinfo, page_key => 'bundles', title => 'Bundles',
51 body => qq~<div class="module"><div class="module-body" style="padding:40px;text-align:center">
52 <h1 style="font-family:var(--font-display);color:#fff">Bundles not installed yet</h1>
53 <p style="color:var(--col-text-2);margin-top:12px">Run the bundles migration from <code>installation_instructions.html</code> &sect; 5.15c, then refresh.</p>
54 </div></div>~,
55 });
56 exit;
57}
58
59# ---- POST handler ----------------------------------------------------
60if (($ENV{REQUEST_METHOD} || '') eq 'POST') {
61 my $act = lc($form->{_act} || ''); $act =~ s/[^a-z_]//g;
62 if ($act eq 'create') {
63 my $id = $bun->create($db, $dbh, $DB, $owner_uid,
64 title => $form->{title},
65 tagline => $form->{tagline},
66 description => $form->{description},
67 product_kind => $form->{product_kind} || 'digital',
68 base_price => $form->{base_price},
69 physical_price => $form->{physical_price},
70 inventory_qty => $form->{inventory_qty},
71 production_time_days => $form->{production_time_days},
72 weight_grams => $form->{weight_grams},
73 ship_from_country => $form->{ship_from_country},
74 shipping_options_json => $form->{shipping_options_json},
75 );
76 $db->db_disconnect($dbh);
77 my $q = $id ? "id=$id&ok=1" : 'err=1';
78 print "Status: 302 Found\nLocation: /bundles.cgi?$q\n\n"; exit;
79 }
80 if ($act eq 'update') {
81 my $id = $form->{id}; $id =~ s/[^0-9]//g;
82 $bun->update($db, $dbh, $DB, $id, $owner_uid,
83 title => $form->{title},
84 tagline => $form->{tagline},
85 description => $form->{description},
86 product_kind => $form->{product_kind},
87 base_price => $form->{base_price},
88 physical_price => $form->{physical_price},
89 inventory_qty => $form->{inventory_qty},
90 production_time_days => $form->{production_time_days},
91 weight_grams => $form->{weight_grams},
92 ship_from_country => $form->{ship_from_country},
93 shipping_options_json => $form->{shipping_options_json},
94 );
95 # Items: comma-separated model_ids from the picker.
96 my @ids = grep { length } split /[,\s]+/, ($form->{item_ids} || '');
97 $bun->set_items($db, $dbh, $DB, $id, \@ids, $owner_uid);
98 $db->db_disconnect($dbh);
99 print "Status: 302 Found\nLocation: /bundles.cgi?id=$id&ok=1\n\n"; exit;
100 }
101 if ($act eq 'publish' || $act eq 'archive' || $act eq 'unarchive') {
102 my $id = $form->{id}; $id =~ s/[^0-9]//g;
103 my $new_status = $act eq 'publish' ? 'published'
104 : $act eq 'archive' ? 'archived' : 'draft';
105 $bun->update($db, $dbh, $DB, $id, $owner_uid, status => $new_status);
106 $db->db_disconnect($dbh);
107 print "Status: 302 Found\nLocation: /bundles.cgi?id=$id&ok=1\n\n"; exit;
108 }
109 if ($act eq 'delete') {
110 my $id = $form->{id}; $id =~ s/[^0-9]//g;
111 $bun->delete($db, $dbh, $DB, $id, $owner_uid);
112 $db->db_disconnect($dbh);
113 print "Status: 302 Found\nLocation: /bundles.cgi?deleted=1\n\n"; exit;
114 }
115 $db->db_disconnect($dbh);
116 print "Status: 302 Found\nLocation: /bundles.cgi\n\n"; exit;
117}
118
119# ---- GET ------------------------------------------------------------
120# ?id=new -> render the empty editor (POST will _act=create).
121# ?id=N -> render the editor pre-filled.
122# (no id) -> render the bundle list.
123my $is_new_form = (defined $form->{id} && $form->{id} eq 'new') ? 1 : 0;
124my $edit_id = '';
125if (defined $form->{id} && !$is_new_form) {
126 $edit_id = $form->{id}; $edit_id =~ s/[^0-9]//g;
127}
128my $editing = ($edit_id) ? $bun->by_id($db, $dbh, $DB, $edit_id, $owner_uid) : undef;
129
130# Pull seller's published models for the item picker.
131my @candidate_rows = $db->db_readwrite_multiple($dbh, qq~
132 SELECT id, title, base_price_cents, physical_price_cents, product_kind, hero_image_url
133 FROM `${DB}`.models
134 WHERE user_id='$owner_uid' AND purged_at IS NULL
135 AND status IN ('published','draft','unlisted')
136 ORDER BY updated_at DESC, id DESC
137 LIMIT 200
138~, $ENV{SCRIPT_NAME}, __LINE__);
139
140my %selected_ids;
141my @existing_items;
142if ($editing) {
143 my $items = $bun->items_for_bundle($db, $dbh, $DB, $editing->{id});
144 foreach my $it (@$items) {
145 $selected_ids{$it->{model_id}} = 1;
146 push @existing_items, {
147 id => $it->{model_id},
148 title => _h($it->{title}),
149 price => '$' . sprintf('%.2f', ($it->{base_price_cents} || 0) / 100),
150 };
151 }
152}
153
154my @candidates;
155foreach my $m (@candidate_rows) {
156 push @candidates, {
157 id => $m->{id},
158 title => _h($m->{title}),
159 kind => $m->{product_kind} || 'digital',
160 price => '$' . sprintf('%.2f', ($m->{base_price_cents} || 0) / 100),
161 is_selected => $selected_ids{$m->{id}} ? 1 : 0,
162 };
163}
164
165my @list_rows;
166foreach my $b (@{ $bun->list_for_user($db, $dbh, $DB, $owner_uid) }) {
167 push @list_rows, {
168 id => $b->{id},
169 title => _h($b->{title}),
170 tagline => _h($b->{tagline} || ''),
171 kind => $b->{product_kind},
172 item_count => $b->{item_count} || 0,
173 status => $b->{status},
174 is_published=> $b->{status} eq 'published' ? 1 : 0,
175 is_archived => $b->{status} eq 'archived' ? 1 : 0,
176 is_draft => $b->{status} eq 'draft' ? 1 : 0,
177 price_label => $b->{product_kind} eq 'physical'
178 ? '$' . sprintf('%.2f', ($b->{physical_price_cents} || 0) / 100)
179 : '$' . sprintf('%.2f', ($b->{base_price_cents} || 0) / 100),
180 };
181}
182
183$db->db_disconnect($dbh);
184
185my $flash_msg = '';
186my $flash_kind = '';
187if (defined $form->{ok}) { $flash_msg = 'Saved.'; $flash_kind = 'ok'; }
188if (defined $form->{deleted}) { $flash_msg = 'Bundle deleted.'; $flash_kind = 'ok'; }
189if (defined $form->{err}) { $flash_msg = 'Save failed -- title is required.'; $flash_kind = 'danger'; }
190
191my $tvars = {
192 has_bundles => scalar(@list_rows) ? 1 : 0,
193 bundles => \@list_rows,
194 is_editing => ($editing || $is_new_form) ? 1 : 0,
195 is_new_form => $is_new_form,
196 editing => $editing,
197 candidates => \@candidates,
198 has_candidates => scalar(@candidates) ? 1 : 0,
199 selected_ids => join(',', sort { $a <=> $b } keys %selected_ids),
200 existing_items => \@existing_items,
201 flash_msg => $flash_msg,
202 flash_kind => $flash_kind,
203 has_flash => length $flash_msg ? 1 : 0,
204};
205
206# Defaults for the form fields the template will read whether create or edit.
207my %defaults = (
208 edit_id => $editing ? $editing->{id} : '',
209 title => $editing ? _h($editing->{title}) : '',
210 tagline => $editing ? _h($editing->{tagline} || '') : '',
211 description => $editing ? _h($editing->{description} || '') : '',
212 product_kind => $editing ? $editing->{product_kind} : 'digital',
213 base_price => $editing ? sprintf('%.2f', ($editing->{base_price_cents} || 0) / 100) : '',
214 physical_price => $editing ? sprintf('%.2f', ($editing->{physical_price_cents} || 0) / 100) : '',
215 inventory_qty => $editing && $editing->{inventory_qty} >= 0 ? $editing->{inventory_qty} : '',
216 production_time_days => $editing ? ($editing->{production_time_days} || '') : '',
217 weight_grams => $editing ? ($editing->{weight_grams} || '') : '',
218 ship_from_country => $editing ? _h($editing->{ship_from_country} || '') : '',
219 shipping_options_json=> $editing ? _h($editing->{shipping_options_json} || '') : '',
220 status_label => $editing ? ucfirst($editing->{status}) : 'New',
221);
222$tvars->{$_} = $defaults{$_} for keys %defaults;
223$tvars->{kind_digital_sel} = ($tvars->{product_kind} eq 'digital') ? 'selected' : '';
224$tvars->{kind_physical_sel} = ($tvars->{product_kind} eq 'physical') ? 'selected' : '';
225$tvars->{kind_both_sel} = ($tvars->{product_kind} eq 'both') ? 'selected' : '';
226
227print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n";
228my $body = join('', $tfile->template('repricer_bundles.html', $tvars, $userinfo));
229$wrap->render({
230 userinfo => $userinfo, page_key => 'bundles', title => 'Bundles', body => $body,
231});
232exit;
233
234sub _h {
235 my $s = shift // '';
236 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g;
237 $s =~ s/"/&quot;/g; $s =~ s/'/&#39;/g;
238 return $s;
239}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help