wiki-plugin-shoppe 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/shoppe.js +48 -4
- package/package.json +1 -1
- package/server/server.js +44 -8
package/client/shoppe.js
CHANGED
|
@@ -103,9 +103,16 @@
|
|
|
103
103
|
<div id="sw-upload-status" class="sw-status"></div>
|
|
104
104
|
</div>
|
|
105
105
|
|
|
106
|
-
<!-- Owner: register -->
|
|
106
|
+
<!-- Owner: config + register -->
|
|
107
107
|
<div class="sw-section" id="sw-owner-section" style="display:none">
|
|
108
|
-
<h3>
|
|
108
|
+
<h3>Allyabase connection (owner only)</h3>
|
|
109
|
+
<div class="sw-register">
|
|
110
|
+
<input type="text" id="sw-url-input" placeholder="https://dojo.allyabase.com/plugin/allyabase/sanora">
|
|
111
|
+
<button class="sw-btn sw-btn-blue" id="sw-url-btn">Save</button>
|
|
112
|
+
</div>
|
|
113
|
+
<div id="sw-url-status" class="sw-status"></div>
|
|
114
|
+
|
|
115
|
+
<h3 style="margin-top:20px">Register a new shoppe (owner only)</h3>
|
|
109
116
|
<div class="sw-register">
|
|
110
117
|
<input type="text" id="sw-name-input" placeholder="Shoppe name (e.g. Zach's Art Store)">
|
|
111
118
|
<button class="sw-btn sw-btn-green" id="sw-register-btn">Register</button>
|
|
@@ -153,9 +160,13 @@
|
|
|
153
160
|
|
|
154
161
|
async function checkOwner(container) {
|
|
155
162
|
try {
|
|
156
|
-
const resp = await fetch('/plugin/shoppe/
|
|
163
|
+
const resp = await fetch('/plugin/shoppe/config');
|
|
157
164
|
if (resp.ok) {
|
|
158
165
|
container.querySelector('#sw-owner-section').style.display = 'block';
|
|
166
|
+
const result = await resp.json();
|
|
167
|
+
if (result.sanoraUrl) {
|
|
168
|
+
container.querySelector('#sw-url-input').value = result.sanoraUrl;
|
|
169
|
+
}
|
|
159
170
|
}
|
|
160
171
|
} catch (err) { /* not owner, stay hidden */ }
|
|
161
172
|
}
|
|
@@ -163,11 +174,17 @@
|
|
|
163
174
|
// ── Listeners ───────────────────────────────────────────────────────────────
|
|
164
175
|
|
|
165
176
|
function setupListeners(container) {
|
|
166
|
-
const drop
|
|
177
|
+
const drop = container.querySelector('#sw-drop');
|
|
167
178
|
const fileInput = container.querySelector('#sw-file-input');
|
|
168
179
|
const browseBtn = container.querySelector('#sw-browse-btn');
|
|
169
180
|
const registerBtn = container.querySelector('#sw-register-btn');
|
|
170
181
|
const nameInput = container.querySelector('#sw-name-input');
|
|
182
|
+
const urlBtn = container.querySelector('#sw-url-btn');
|
|
183
|
+
const urlInput = container.querySelector('#sw-url-input');
|
|
184
|
+
|
|
185
|
+
if (urlBtn) {
|
|
186
|
+
urlBtn.addEventListener('click', () => saveUrl(container));
|
|
187
|
+
}
|
|
171
188
|
|
|
172
189
|
browseBtn.addEventListener('click', () => fileInput.click());
|
|
173
190
|
fileInput.addEventListener('change', e => {
|
|
@@ -222,6 +239,33 @@
|
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
241
|
|
|
242
|
+
// ── Save URL (owner) ────────────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
async function saveUrl(container) {
|
|
245
|
+
const urlInput = container.querySelector('#sw-url-input');
|
|
246
|
+
const urlBtn = container.querySelector('#sw-url-btn');
|
|
247
|
+
const url = urlInput.value.trim();
|
|
248
|
+
if (!url) { showStatus(container, '#sw-url-status', 'Enter an allyabase URL first', 'error'); return; }
|
|
249
|
+
|
|
250
|
+
urlBtn.disabled = true;
|
|
251
|
+
urlBtn.textContent = 'Saving…';
|
|
252
|
+
try {
|
|
253
|
+
const resp = await fetch('/plugin/shoppe/config', {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: { 'Content-Type': 'application/json' },
|
|
256
|
+
body: JSON.stringify({ sanoraUrl: url })
|
|
257
|
+
});
|
|
258
|
+
const result = await resp.json();
|
|
259
|
+
if (!result.success) throw new Error(result.error || 'Save failed');
|
|
260
|
+
showStatus(container, '#sw-url-status', `✅ Connected to <strong>${url}</strong>`, 'success');
|
|
261
|
+
} catch (err) {
|
|
262
|
+
showStatus(container, '#sw-url-status', `❌ ${err.message}`, 'error');
|
|
263
|
+
} finally {
|
|
264
|
+
urlBtn.disabled = false;
|
|
265
|
+
urlBtn.textContent = 'Save';
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
225
269
|
// ── Register (owner) ────────────────────────────────────────────────────────
|
|
226
270
|
|
|
227
271
|
async function registerShoppe(container) {
|
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -7,12 +7,31 @@ const FormData = require('form-data');
|
|
|
7
7
|
const AdmZip = require('adm-zip');
|
|
8
8
|
const sessionless = require('sessionless-node');
|
|
9
9
|
|
|
10
|
-
const SANORA_PORT = process.env.SANORA_PORT || 7243;
|
|
11
10
|
const SHOPPE_BASE_EMOJI = process.env.SHOPPE_BASE_EMOJI || '🛍️🎨🎁';
|
|
12
11
|
|
|
13
12
|
const TENANTS_FILE = path.join(__dirname, '../.shoppe-tenants.json');
|
|
13
|
+
const CONFIG_FILE = path.join(__dirname, '../.shoppe-config.json');
|
|
14
14
|
const TMP_DIR = '/tmp/shoppe-uploads';
|
|
15
15
|
|
|
16
|
+
// ============================================================
|
|
17
|
+
// CONFIG (allyabase URL, etc.)
|
|
18
|
+
// ============================================================
|
|
19
|
+
|
|
20
|
+
function loadConfig() {
|
|
21
|
+
if (!fs.existsSync(CONFIG_FILE)) return {};
|
|
22
|
+
try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch (e) { return {}; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function saveConfig(config) {
|
|
26
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getSanoraUrl() {
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
if (config.sanoraUrl) return config.sanoraUrl.replace(/\/$/, '');
|
|
32
|
+
return `http://localhost:${process.env.SANORA_PORT || 7243}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
// Same diverse palette as BDO emojicoding
|
|
17
36
|
const EMOJI_PALETTE = [
|
|
18
37
|
'🌟', '🌙', '🌍', '🌊', '🔥', '💎', '🎨', '🎭', '🎪', '🎯',
|
|
@@ -70,7 +89,7 @@ async function registerTenant(name) {
|
|
|
70
89
|
const message = timestamp + keys.pubKey;
|
|
71
90
|
const signature = await sessionless.sign(message);
|
|
72
91
|
|
|
73
|
-
const resp = await fetch(
|
|
92
|
+
const resp = await fetch(`${getSanoraUrl()}/user/create`, {
|
|
74
93
|
method: 'PUT',
|
|
75
94
|
headers: { 'Content-Type': 'application/json' },
|
|
76
95
|
body: JSON.stringify({ timestamp, pubKey: keys.pubKey, signature })
|
|
@@ -138,7 +157,7 @@ async function sanoraCreateProduct(tenant, title, category, description, price,
|
|
|
138
157
|
const signature = await sessionless.sign(message);
|
|
139
158
|
|
|
140
159
|
const resp = await fetch(
|
|
141
|
-
|
|
160
|
+
`${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}`,
|
|
142
161
|
{
|
|
143
162
|
method: 'PUT',
|
|
144
163
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -171,7 +190,7 @@ async function sanoraUploadArtifact(tenant, title, fileBuffer, filename, artifac
|
|
|
171
190
|
form.append('artifact', fileBuffer, { filename, contentType: getMimeType(filename) });
|
|
172
191
|
|
|
173
192
|
const resp = await fetch(
|
|
174
|
-
|
|
193
|
+
`${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}/artifact`,
|
|
175
194
|
{
|
|
176
195
|
method: 'PUT',
|
|
177
196
|
headers: {
|
|
@@ -200,7 +219,7 @@ async function sanoraUploadImage(tenant, title, imageBuffer, filename) {
|
|
|
200
219
|
form.append('image', imageBuffer, { filename, contentType: getMimeType(filename) });
|
|
201
220
|
|
|
202
221
|
const resp = await fetch(
|
|
203
|
-
|
|
222
|
+
`${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}/image`,
|
|
204
223
|
{
|
|
205
224
|
method: 'PUT',
|
|
206
225
|
headers: {
|
|
@@ -401,7 +420,7 @@ async function processArchive(zipPath) {
|
|
|
401
420
|
// ============================================================
|
|
402
421
|
|
|
403
422
|
async function getShoppeGoods(tenant) {
|
|
404
|
-
const resp = await fetch(
|
|
423
|
+
const resp = await fetch(`${getSanoraUrl()}/products/${tenant.uuid}`);
|
|
405
424
|
const products = await resp.json();
|
|
406
425
|
|
|
407
426
|
const goods = { books: [], music: [], posts: [], albums: [], products: [] };
|
|
@@ -412,8 +431,8 @@ async function getShoppeGoods(tenant) {
|
|
|
412
431
|
description: product.description || '',
|
|
413
432
|
price: product.price || 0,
|
|
414
433
|
shipping: product.shipping || 0,
|
|
415
|
-
image: product.image ?
|
|
416
|
-
url:
|
|
434
|
+
image: product.image ? `${getSanoraUrl()}/images/${product.image}` : null,
|
|
435
|
+
url: `${getSanoraUrl()}/products/${tenant.uuid}/${encodeURIComponent(title)}`
|
|
417
436
|
};
|
|
418
437
|
const bucket = goods[product.category];
|
|
419
438
|
if (bucket) bucket.push(item);
|
|
@@ -597,6 +616,23 @@ async function startServer(params) {
|
|
|
597
616
|
}
|
|
598
617
|
});
|
|
599
618
|
|
|
619
|
+
// Get config (owner only)
|
|
620
|
+
app.get('/plugin/shoppe/config', owner, (req, res) => {
|
|
621
|
+
const config = loadConfig();
|
|
622
|
+
res.json({ success: true, sanoraUrl: config.sanoraUrl || '' });
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// Save config (owner only)
|
|
626
|
+
app.post('/plugin/shoppe/config', owner, (req, res) => {
|
|
627
|
+
const { sanoraUrl } = req.body;
|
|
628
|
+
if (!sanoraUrl) return res.status(400).json({ success: false, error: 'sanoraUrl required' });
|
|
629
|
+
const config = loadConfig();
|
|
630
|
+
config.sanoraUrl = sanoraUrl;
|
|
631
|
+
saveConfig(config);
|
|
632
|
+
console.log('[shoppe] Sanora URL set to:', sanoraUrl);
|
|
633
|
+
res.json({ success: true });
|
|
634
|
+
});
|
|
635
|
+
|
|
600
636
|
// Goods JSON (public)
|
|
601
637
|
app.get('/plugin/shoppe/:identifier/goods', async (req, res) => {
|
|
602
638
|
try {
|