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 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.4",
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
  '🌟', '🌙', '🌍', '🌊', '🔥', '💎', '🎨', '🎭', '🎪', '🎯',
@@ -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, keys.privateKey);
90
+ const signature = await sessionless.sign(message);
71
91
 
72
- const resp = await fetch(`http://localhost:${SANORA_PORT}/user/create`, {
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
- `http://localhost:${SANORA_PORT}/user/${uuid}/product/${encodeURIComponent(title)}`,
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
- `http://localhost:${SANORA_PORT}/user/${uuid}/product/${encodeURIComponent(title)}/artifact`,
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
- `http://localhost:${SANORA_PORT}/user/${uuid}/product/${encodeURIComponent(title)}/image`,
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(`http://localhost:${SANORA_PORT}/products/${tenant.uuid}`);
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 ? `http://localhost:${SANORA_PORT}/images/${product.image}` : null,
415
- 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)}`
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 {