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 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>Register a new shoppe (owner only)</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/tenants');
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 = container.querySelector('#sw-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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-plugin-shoppe",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Multi-tenant digital goods shoppe for federated wiki, powered by Sanora",
5
5
  "keywords": [
6
6
  "wiki",
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(`http://localhost:${SANORA_PORT}/user/create`, {
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
- `http://localhost:${SANORA_PORT}/user/${uuid}/product/${encodeURIComponent(title)}`,
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
- `http://localhost:${SANORA_PORT}/user/${uuid}/product/${encodeURIComponent(title)}/artifact`,
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
- `http://localhost:${SANORA_PORT}/user/${uuid}/product/${encodeURIComponent(title)}/image`,
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(`http://localhost:${SANORA_PORT}/products/${tenant.uuid}`);
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 ? `http://localhost:${SANORA_PORT}/images/${product.image}` : null,
416
- url: `http://localhost:${SANORA_PORT}/products/${tenant.uuid}/${encodeURIComponent(title)}`
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 {