wiki-plugin-shoppe 0.0.4 → 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 +46 -9
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
|
'🌟', '🌙', '🌍', '🌊', '🔥', '💎', '🎨', '🎭', '🎪', '🎯',
|
|
@@ -65,11 +84,12 @@ async function registerTenant(name) {
|
|
|
65
84
|
|
|
66
85
|
// Create a dedicated Sanora user for this tenant
|
|
67
86
|
const keys = await sessionless.generateKeys(() => {}, () => null);
|
|
87
|
+
sessionless.getKeys = () => keys;
|
|
68
88
|
const timestamp = Date.now().toString();
|
|
69
89
|
const message = timestamp + keys.pubKey;
|
|
70
|
-
const signature = await sessionless.sign(message
|
|
90
|
+
const signature = await sessionless.sign(message);
|
|
71
91
|
|
|
72
|
-
const resp = await fetch(
|
|
92
|
+
const resp = await fetch(`${getSanoraUrl()}/user/create`, {
|
|
73
93
|
method: 'PUT',
|
|
74
94
|
headers: { 'Content-Type': 'application/json' },
|
|
75
95
|
body: JSON.stringify({ timestamp, pubKey: keys.pubKey, signature })
|
|
@@ -137,7 +157,7 @@ async function sanoraCreateProduct(tenant, title, category, description, price,
|
|
|
137
157
|
const signature = await sessionless.sign(message);
|
|
138
158
|
|
|
139
159
|
const resp = await fetch(
|
|
140
|
-
|
|
160
|
+
`${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}`,
|
|
141
161
|
{
|
|
142
162
|
method: 'PUT',
|
|
143
163
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -170,7 +190,7 @@ async function sanoraUploadArtifact(tenant, title, fileBuffer, filename, artifac
|
|
|
170
190
|
form.append('artifact', fileBuffer, { filename, contentType: getMimeType(filename) });
|
|
171
191
|
|
|
172
192
|
const resp = await fetch(
|
|
173
|
-
|
|
193
|
+
`${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}/artifact`,
|
|
174
194
|
{
|
|
175
195
|
method: 'PUT',
|
|
176
196
|
headers: {
|
|
@@ -199,7 +219,7 @@ async function sanoraUploadImage(tenant, title, imageBuffer, filename) {
|
|
|
199
219
|
form.append('image', imageBuffer, { filename, contentType: getMimeType(filename) });
|
|
200
220
|
|
|
201
221
|
const resp = await fetch(
|
|
202
|
-
|
|
222
|
+
`${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}/image`,
|
|
203
223
|
{
|
|
204
224
|
method: 'PUT',
|
|
205
225
|
headers: {
|
|
@@ -400,7 +420,7 @@ async function processArchive(zipPath) {
|
|
|
400
420
|
// ============================================================
|
|
401
421
|
|
|
402
422
|
async function getShoppeGoods(tenant) {
|
|
403
|
-
const resp = await fetch(
|
|
423
|
+
const resp = await fetch(`${getSanoraUrl()}/products/${tenant.uuid}`);
|
|
404
424
|
const products = await resp.json();
|
|
405
425
|
|
|
406
426
|
const goods = { books: [], music: [], posts: [], albums: [], products: [] };
|
|
@@ -411,8 +431,8 @@ async function getShoppeGoods(tenant) {
|
|
|
411
431
|
description: product.description || '',
|
|
412
432
|
price: product.price || 0,
|
|
413
433
|
shipping: product.shipping || 0,
|
|
414
|
-
image: product.image ?
|
|
415
|
-
url:
|
|
434
|
+
image: product.image ? `${getSanoraUrl()}/images/${product.image}` : null,
|
|
435
|
+
url: `${getSanoraUrl()}/products/${tenant.uuid}/${encodeURIComponent(title)}`
|
|
416
436
|
};
|
|
417
437
|
const bucket = goods[product.category];
|
|
418
438
|
if (bucket) bucket.push(item);
|
|
@@ -596,6 +616,23 @@ async function startServer(params) {
|
|
|
596
616
|
}
|
|
597
617
|
});
|
|
598
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
|
+
|
|
599
636
|
// Goods JSON (public)
|
|
600
637
|
app.get('/plugin/shoppe/:identifier/goods', async (req, res) => {
|
|
601
638
|
try {
|