wiki-plugin-shoppe 0.0.1 β 0.0.3
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/client.js +204 -144
- package/package.json +2 -2
- package/server/server.js +11 -1
package/client/client.js
CHANGED
|
@@ -4,93 +4,177 @@
|
|
|
4
4
|
emit: function($item, item) {
|
|
5
5
|
const div = $item[0];
|
|
6
6
|
div.innerHTML = `
|
|
7
|
-
<div class="
|
|
7
|
+
<div class="sw">
|
|
8
8
|
<style>
|
|
9
|
-
.
|
|
10
|
-
.
|
|
11
|
-
.
|
|
12
|
-
.
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.
|
|
19
|
-
.
|
|
20
|
-
.
|
|
21
|
-
.shoppe
|
|
22
|
-
.shoppe-
|
|
23
|
-
.shoppe-
|
|
24
|
-
.shoppe-
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
.
|
|
33
|
-
.
|
|
34
|
-
.
|
|
9
|
+
.sw { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 680px; margin: 0 auto; color: #1d1d1f; }
|
|
10
|
+
.sw h2 { font-size: 22px; font-weight: 700; margin: 0 0 4px; }
|
|
11
|
+
.sw h3 { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.5px; margin: 0 0 10px; }
|
|
12
|
+
.sw-section { margin-bottom: 24px; }
|
|
13
|
+
.sw-card { background: #f5f5f7; border-radius: 12px; padding: 18px 20px; margin-bottom: 10px; }
|
|
14
|
+
.sw-step { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 12px; }
|
|
15
|
+
.sw-step:last-child { margin-bottom: 0; }
|
|
16
|
+
.sw-step-num { background: #0066cc; color: white; border-radius: 50%; width: 24px; height: 24px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; margin-top: 1px; }
|
|
17
|
+
.sw-step-body { font-size: 14px; line-height: 1.5; color: #333; }
|
|
18
|
+
.sw-step-body strong { color: #1d1d1f; }
|
|
19
|
+
.sw-step-body code { background: #e8e8ed; border-radius: 4px; padding: 1px 5px; font-size: 12px; }
|
|
20
|
+
.sw-tree { font-family: monospace; font-size: 12px; background: #1d1d1f; color: #a8f0a8; border-radius: 8px; padding: 14px 16px; line-height: 1.7; white-space: pre; overflow-x: auto; margin-top: 8px; }
|
|
21
|
+
.sw-shoppe { display: flex; align-items: center; justify-content: space-between; background: white; border: 1px solid #e5e5ea; border-radius: 10px; padding: 12px 16px; margin-bottom: 8px; }
|
|
22
|
+
.sw-shoppe-left { display: flex; flex-direction: column; gap: 2px; }
|
|
23
|
+
.sw-shoppe-name { font-weight: 600; font-size: 15px; }
|
|
24
|
+
.sw-shoppe-code { font-size: 18px; letter-spacing: 4px; }
|
|
25
|
+
.sw-link { font-size: 13px; color: #0066cc; text-decoration: none; white-space: nowrap; }
|
|
26
|
+
.sw-link:hover { text-decoration: underline; }
|
|
27
|
+
.sw-empty { font-size: 13px; color: #999; font-style: italic; }
|
|
28
|
+
.sw-drop { border: 2px dashed #ccc; border-radius: 12px; padding: 28px 20px; text-align: center; background: #fafafa; transition: border-color 0.2s, background 0.2s; cursor: pointer; }
|
|
29
|
+
.sw-drop.dragover { border-color: #0066cc; background: #e8f0fe; }
|
|
30
|
+
.sw-drop p { font-size: 13px; color: #888; margin: 6px 0 14px; }
|
|
31
|
+
.sw-btn { display: inline-block; padding: 9px 20px; border-radius: 18px; font-size: 13px; font-weight: 600; cursor: pointer; border: none; transition: background 0.15s; }
|
|
32
|
+
.sw-btn-blue { background: #0066cc; color: white; }
|
|
33
|
+
.sw-btn-blue:hover { background: #0055aa; }
|
|
34
|
+
.sw-btn-green { background: #10b981; color: white; }
|
|
35
|
+
.sw-btn-green:hover { background: #059669; }
|
|
36
|
+
.sw-register { display: flex; gap: 8px; margin-top: 10px; }
|
|
37
|
+
.sw-register input { flex: 1; padding: 9px 13px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; outline: none; }
|
|
38
|
+
.sw-register input:focus { border-color: #0066cc; }
|
|
39
|
+
.sw-status { margin-top: 12px; border-radius: 10px; padding: 13px 16px; font-size: 14px; line-height: 1.5; display: none; }
|
|
40
|
+
.sw-status.info { background: #e8f0fe; color: #1a56db; display: block; }
|
|
41
|
+
.sw-status.success { background: #d1fae5; color: #065f46; display: block; }
|
|
42
|
+
.sw-status.error { background: #fee2e2; color: #991b1b; display: block; }
|
|
43
|
+
.sw-status code { background: rgba(0,0,0,0.08); border-radius: 4px; padding: 1px 5px; font-size: 12px; }
|
|
35
44
|
</style>
|
|
36
45
|
|
|
37
|
-
<!--
|
|
38
|
-
<div class="
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
<input type="file" id="shoppe-file-input" accept=".zip" style="display:none">
|
|
46
|
+
<!-- Directory -->
|
|
47
|
+
<div class="sw-section">
|
|
48
|
+
<h2>ποΈ Shoppe</h2>
|
|
49
|
+
<p style="font-size:14px;color:#555;margin:4px 0 16px">A multi-tenant digital goods marketplace. Browse the shoppes below, or open one of your own.</p>
|
|
50
|
+
<h3>Shoppes on this server</h3>
|
|
51
|
+
<div id="sw-directory"><em class="sw-empty">Loading...</em></div>
|
|
44
52
|
</div>
|
|
45
53
|
|
|
46
|
-
<!--
|
|
47
|
-
<div class="
|
|
48
|
-
<
|
|
49
|
-
<div class="
|
|
50
|
-
<
|
|
51
|
-
|
|
54
|
+
<!-- How to join -->
|
|
55
|
+
<div class="sw-section">
|
|
56
|
+
<h3>How to open a shoppe</h3>
|
|
57
|
+
<div class="sw-card">
|
|
58
|
+
<div class="sw-step">
|
|
59
|
+
<div class="sw-step-num">1</div>
|
|
60
|
+
<div class="sw-step-body"><strong>Ask the wiki owner to register you.</strong> They'll use the form at the bottom of this page and give you a <code>uuid</code> and <code>emojicode</code> β your shoppe's identity.</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="sw-step">
|
|
63
|
+
<div class="sw-step-num">2</div>
|
|
64
|
+
<div class="sw-step-body"><strong>Build your shoppe folder</strong> with this structure, then zip the whole thing:
|
|
65
|
+
<div class="sw-tree">my-shoppe.zip
|
|
66
|
+
manifest.json β { "uuid": "β¦", "emojicode": "β¦", "name": "My Shoppe" }
|
|
67
|
+
books/ β .epub .pdf .mobi
|
|
68
|
+
music/
|
|
69
|
+
My Album/ β subfolder = album (add cover.jpg inside)
|
|
70
|
+
cover.jpg
|
|
71
|
+
01-track.mp3
|
|
72
|
+
standalone.mp3 β file directly here = single track
|
|
73
|
+
posts/ β .md files
|
|
74
|
+
albums/
|
|
75
|
+
Vacation 2025/ β subfolder of images = photo album
|
|
76
|
+
photo1.jpg
|
|
77
|
+
products/
|
|
78
|
+
T-Shirt/ β subfolder = physical product
|
|
79
|
+
cover.jpg
|
|
80
|
+
info.json β { "title": "β¦", "description": "β¦", "price": 25, "shipping": 5 }</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="sw-step">
|
|
84
|
+
<div class="sw-step-num">3</div>
|
|
85
|
+
<div class="sw-step-body"><strong>Drag your .zip onto the upload zone below.</strong> Your goods will be registered and your shoppe will go live immediately.</div>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="sw-step">
|
|
88
|
+
<div class="sw-step-num">4</div>
|
|
89
|
+
<div class="sw-step-body"><strong>To update your shoppe</strong>, just rebuild your folder and upload a new archive β existing items will be overwritten and new ones added.</div>
|
|
90
|
+
</div>
|
|
52
91
|
</div>
|
|
53
92
|
</div>
|
|
54
93
|
|
|
55
|
-
<!--
|
|
56
|
-
<div class="
|
|
57
|
-
<
|
|
58
|
-
<div
|
|
94
|
+
<!-- Upload -->
|
|
95
|
+
<div class="sw-section">
|
|
96
|
+
<h3>Upload your archive</h3>
|
|
97
|
+
<div class="sw-drop" id="sw-drop">
|
|
98
|
+
<div style="font-size:40px">π¦</div>
|
|
99
|
+
<p>Drag and drop your .zip here, or click to browse.<br>Your <code>manifest.json</code> must contain the <code>uuid</code> and <code>emojicode</code> you were given.</p>
|
|
100
|
+
<button class="sw-btn sw-btn-blue" id="sw-browse-btn">Choose Archive</button>
|
|
101
|
+
<input type="file" id="sw-file-input" accept=".zip" style="display:none">
|
|
102
|
+
</div>
|
|
103
|
+
<div id="sw-upload-status" class="sw-status"></div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- Owner: register -->
|
|
107
|
+
<div class="sw-section" id="sw-owner-section" style="display:none">
|
|
108
|
+
<h3>Register a new shoppe (owner only)</h3>
|
|
109
|
+
<div class="sw-register">
|
|
110
|
+
<input type="text" id="sw-name-input" placeholder="Shoppe name (e.g. Zach's Art Store)">
|
|
111
|
+
<button class="sw-btn sw-btn-green" id="sw-register-btn">Register</button>
|
|
112
|
+
</div>
|
|
113
|
+
<div id="sw-register-status" class="sw-status"></div>
|
|
59
114
|
</div>
|
|
60
115
|
|
|
61
|
-
<!-- Status -->
|
|
62
|
-
<div id="shoppe-status" style="display:none"></div>
|
|
63
116
|
</div>
|
|
64
117
|
`;
|
|
65
118
|
|
|
66
|
-
|
|
67
|
-
|
|
119
|
+
setupListeners(div);
|
|
120
|
+
loadDirectory(div);
|
|
121
|
+
checkOwner(div);
|
|
68
122
|
},
|
|
69
123
|
|
|
70
124
|
bind: function($item, item) {}
|
|
71
125
|
};
|
|
72
126
|
|
|
73
|
-
|
|
74
|
-
const drop = container.querySelector('#shoppe-drop');
|
|
75
|
-
const fileInput = container.querySelector('#shoppe-file-input');
|
|
76
|
-
const browseBtn = container.querySelector('#shoppe-browse-btn');
|
|
77
|
-
const registerBtn = container.querySelector('#shoppe-register-btn');
|
|
78
|
-
const nameInput = container.querySelector('#shoppe-name-input');
|
|
127
|
+
// ββ Directory (public) ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
79
128
|
|
|
80
|
-
|
|
81
|
-
|
|
129
|
+
async function loadDirectory(container) {
|
|
130
|
+
const el = container.querySelector('#sw-directory');
|
|
131
|
+
try {
|
|
132
|
+
const resp = await fetch('/plugin/shoppe/directory');
|
|
133
|
+
const result = await resp.json();
|
|
134
|
+
if (!result.success || result.shoppes.length === 0) {
|
|
135
|
+
el.innerHTML = '<em class="sw-empty">No shoppes yet β be the first!</em>';
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
el.innerHTML = result.shoppes.map(s => `
|
|
139
|
+
<div class="sw-shoppe">
|
|
140
|
+
<div class="sw-shoppe-left">
|
|
141
|
+
<span class="sw-shoppe-name">${s.name}</span>
|
|
142
|
+
<span class="sw-shoppe-code">${s.emojicode}</span>
|
|
143
|
+
</div>
|
|
144
|
+
<a class="sw-link" href="${s.url}" target="_blank">Visit shoppe β</a>
|
|
145
|
+
</div>
|
|
146
|
+
`).join('');
|
|
147
|
+
} catch (err) {
|
|
148
|
+
el.innerHTML = '<em class="sw-empty">Could not load directory.</em>';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ββ Owner check βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
153
|
+
|
|
154
|
+
async function checkOwner(container) {
|
|
155
|
+
try {
|
|
156
|
+
const resp = await fetch('/plugin/shoppe/tenants');
|
|
157
|
+
if (resp.ok) {
|
|
158
|
+
container.querySelector('#sw-owner-section').style.display = 'block';
|
|
159
|
+
}
|
|
160
|
+
} catch (err) { /* not owner, stay hidden */ }
|
|
161
|
+
}
|
|
82
162
|
|
|
83
|
-
|
|
163
|
+
// ββ Listeners βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
164
|
+
|
|
165
|
+
function setupListeners(container) {
|
|
166
|
+
const drop = container.querySelector('#sw-drop');
|
|
167
|
+
const fileInput = container.querySelector('#sw-file-input');
|
|
168
|
+
const browseBtn = container.querySelector('#sw-browse-btn');
|
|
169
|
+
const registerBtn = container.querySelector('#sw-register-btn');
|
|
170
|
+
const nameInput = container.querySelector('#sw-name-input');
|
|
171
|
+
|
|
172
|
+
browseBtn.addEventListener('click', () => fileInput.click());
|
|
84
173
|
fileInput.addEventListener('change', e => {
|
|
85
|
-
|
|
86
|
-
if (file) uploadArchive(file, container);
|
|
174
|
+
if (e.target.files[0]) uploadArchive(e.target.files[0], container);
|
|
87
175
|
});
|
|
88
176
|
|
|
89
|
-
|
|
90
|
-
drop.addEventListener('dragover', e => {
|
|
91
|
-
e.preventDefault();
|
|
92
|
-
drop.classList.add('dragover');
|
|
93
|
-
});
|
|
177
|
+
drop.addEventListener('dragover', e => { e.preventDefault(); drop.classList.add('dragover'); });
|
|
94
178
|
drop.addEventListener('dragleave', () => drop.classList.remove('dragover'));
|
|
95
179
|
drop.addEventListener('drop', e => {
|
|
96
180
|
e.preventDefault();
|
|
@@ -99,109 +183,85 @@
|
|
|
99
183
|
if (file && file.name.endsWith('.zip')) {
|
|
100
184
|
uploadArchive(file, container);
|
|
101
185
|
} else {
|
|
102
|
-
showStatus(container, 'Please drop a .zip archive', 'error');
|
|
186
|
+
showStatus(container, '#sw-upload-status', 'Please drop a .zip archive', 'error');
|
|
103
187
|
}
|
|
104
188
|
});
|
|
105
189
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!name) { showStatus(container, 'Enter a shoppe name first', 'error'); return; }
|
|
110
|
-
registerBtn.disabled = true;
|
|
111
|
-
registerBtn.textContent = 'Registering...';
|
|
112
|
-
try {
|
|
113
|
-
const resp = await fetch('/plugin/shoppe/register', {
|
|
114
|
-
method: 'POST',
|
|
115
|
-
headers: { 'Content-Type': 'application/json' },
|
|
116
|
-
body: JSON.stringify({ name })
|
|
117
|
-
});
|
|
118
|
-
const result = await resp.json();
|
|
119
|
-
if (result.success) {
|
|
120
|
-
nameInput.value = '';
|
|
121
|
-
showStatus(container,
|
|
122
|
-
`β
Registered! UUID: <code>${result.tenant.uuid}</code><br>Emojicode: <strong>${result.tenant.emojicode}</strong><br>Put these in your manifest.json`,
|
|
123
|
-
'success');
|
|
124
|
-
loadTenants(container);
|
|
125
|
-
} else {
|
|
126
|
-
throw new Error(result.error || 'Registration failed');
|
|
127
|
-
}
|
|
128
|
-
} catch (err) {
|
|
129
|
-
showStatus(container, `β ${err.message}`, 'error');
|
|
130
|
-
} finally {
|
|
131
|
-
registerBtn.disabled = false;
|
|
132
|
-
registerBtn.textContent = 'Register';
|
|
133
|
-
}
|
|
134
|
-
});
|
|
190
|
+
if (registerBtn) {
|
|
191
|
+
registerBtn.addEventListener('click', () => registerShoppe(container));
|
|
192
|
+
}
|
|
135
193
|
}
|
|
136
194
|
|
|
137
|
-
|
|
138
|
-
showStatus(container, `β³ Uploading and processing <strong>${file.name}</strong>...`, 'info');
|
|
195
|
+
// ββ Upload ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
139
196
|
|
|
197
|
+
async function uploadArchive(file, container) {
|
|
198
|
+
showStatus(container, '#sw-upload-status', `β³ Uploading <strong>${file.name}</strong>β¦`, 'info');
|
|
140
199
|
const form = new FormData();
|
|
141
200
|
form.append('archive', file);
|
|
142
|
-
|
|
143
201
|
try {
|
|
144
202
|
const resp = await fetch('/plugin/shoppe/upload', { method: 'POST', body: form });
|
|
145
203
|
const result = await resp.json();
|
|
204
|
+
if (!result.success) throw new Error(result.error || 'Upload failed');
|
|
146
205
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
'success');
|
|
162
|
-
loadTenants(container);
|
|
163
|
-
} else {
|
|
164
|
-
throw new Error(result.error || 'Upload failed');
|
|
165
|
-
}
|
|
206
|
+
const r = result.results;
|
|
207
|
+
const counts = [
|
|
208
|
+
r.books.length && `π ${r.books.length} book${r.books.length !== 1 ? 's' : ''}`,
|
|
209
|
+
r.music.length && `π΅ ${r.music.length} music item${r.music.length !== 1 ? 's' : ''}`,
|
|
210
|
+
r.posts.length && `π ${r.posts.length} post${r.posts.length !== 1 ? 's' : ''}`,
|
|
211
|
+
r.albums.length && `πΌοΈ ${r.albums.length} album${r.albums.length !== 1 ? 's' : ''}`,
|
|
212
|
+
r.products.length && `π¦ ${r.products.length} product${r.products.length !== 1 ? 's' : ''}`
|
|
213
|
+
].filter(Boolean).join(' Β· ') || 'no items found';
|
|
214
|
+
|
|
215
|
+
showStatus(container, '#sw-upload-status',
|
|
216
|
+
`β
<strong>${result.tenant.name}</strong> ${result.tenant.emojicode} updated β ${counts}<br>
|
|
217
|
+
<a href="/plugin/shoppe/${result.tenant.uuid}" target="_blank" class="sw-link" style="display:inline-block;margin-top:8px;">View your shoppe β</a>`,
|
|
218
|
+
'success');
|
|
219
|
+
loadDirectory(container);
|
|
166
220
|
} catch (err) {
|
|
167
|
-
showStatus(container, `β ${err.message}`, 'error');
|
|
221
|
+
showStatus(container, '#sw-upload-status', `β ${err.message}`, 'error');
|
|
168
222
|
}
|
|
169
223
|
}
|
|
170
224
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
225
|
+
// ββ Register (owner) ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
226
|
+
|
|
227
|
+
async function registerShoppe(container) {
|
|
228
|
+
const nameInput = container.querySelector('#sw-name-input');
|
|
229
|
+
const registerBtn = container.querySelector('#sw-register-btn');
|
|
230
|
+
const name = nameInput.value.trim();
|
|
231
|
+
if (!name) { showStatus(container, '#sw-register-status', 'Enter a shoppe name first', 'error'); return; }
|
|
232
|
+
|
|
233
|
+
registerBtn.disabled = true;
|
|
234
|
+
registerBtn.textContent = 'Registeringβ¦';
|
|
174
235
|
try {
|
|
175
|
-
const resp = await fetch('/plugin/shoppe/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
236
|
+
const resp = await fetch('/plugin/shoppe/register', {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: { 'Content-Type': 'application/json' },
|
|
239
|
+
body: JSON.stringify({ name })
|
|
240
|
+
});
|
|
180
241
|
const result = await resp.json();
|
|
181
|
-
if (!result.success
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<code style="font-size:11px;color:#888">${t.uuid}</code>
|
|
191
|
-
</div>
|
|
192
|
-
<a class="shoppe-tenant-link" href="${t.url}" target="_blank">View shoppe β</a>
|
|
193
|
-
</div>
|
|
194
|
-
`).join('');
|
|
242
|
+
if (!result.success) throw new Error(result.error || 'Registration failed');
|
|
243
|
+
|
|
244
|
+
nameInput.value = '';
|
|
245
|
+
showStatus(container, '#sw-register-status',
|
|
246
|
+
`β
Registered! Give these to the shoppe owner:<br>
|
|
247
|
+
UUID: <code>${result.tenant.uuid}</code><br>
|
|
248
|
+
Emojicode: <strong>${result.tenant.emojicode}</strong>`,
|
|
249
|
+
'success');
|
|
250
|
+
loadDirectory(container);
|
|
195
251
|
} catch (err) {
|
|
196
|
-
|
|
252
|
+
showStatus(container, '#sw-register-status', `β ${err.message}`, 'error');
|
|
253
|
+
} finally {
|
|
254
|
+
registerBtn.disabled = false;
|
|
255
|
+
registerBtn.textContent = 'Register';
|
|
197
256
|
}
|
|
198
257
|
}
|
|
199
258
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
259
|
+
// ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
260
|
+
|
|
261
|
+
function showStatus(container, selector, html, type) {
|
|
262
|
+
const el = container.querySelector(selector);
|
|
263
|
+
el.className = `sw-status ${type}`;
|
|
203
264
|
el.innerHTML = html;
|
|
204
|
-
el.style.display = 'block';
|
|
205
265
|
}
|
|
206
266
|
|
|
207
267
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-plugin-shoppe",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Multi-tenant digital goods shoppe for federated wiki, powered by Sanora",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wiki",
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"form-data": "^4.0.0",
|
|
23
23
|
"multer": "^1.4.5-lts.1",
|
|
24
24
|
"node-fetch": "^2.6.1",
|
|
25
|
-
"sessionless-node": "
|
|
25
|
+
"sessionless-node": "latest"
|
|
26
26
|
}
|
|
27
27
|
}
|
package/server/server.js
CHANGED
|
@@ -557,7 +557,7 @@ async function startServer(params) {
|
|
|
557
557
|
}
|
|
558
558
|
});
|
|
559
559
|
|
|
560
|
-
// List all tenants (owner only)
|
|
560
|
+
// List all tenants (owner only β includes uuid for management)
|
|
561
561
|
app.get('/plugin/shoppe/tenants', owner, (req, res) => {
|
|
562
562
|
const tenants = loadTenants();
|
|
563
563
|
const safe = Object.values(tenants).map(({ uuid, emojicode, name, createdAt }) => ({
|
|
@@ -567,6 +567,16 @@ async function startServer(params) {
|
|
|
567
567
|
res.json({ success: true, tenants: safe });
|
|
568
568
|
});
|
|
569
569
|
|
|
570
|
+
// Public directory β name, emojicode, and shoppe URL only
|
|
571
|
+
app.get('/plugin/shoppe/directory', (req, res) => {
|
|
572
|
+
const tenants = loadTenants();
|
|
573
|
+
const listing = Object.values(tenants).map(({ uuid, emojicode, name }) => ({
|
|
574
|
+
name, emojicode,
|
|
575
|
+
url: `/plugin/shoppe/${uuid}`
|
|
576
|
+
}));
|
|
577
|
+
res.json({ success: true, shoppes: listing });
|
|
578
|
+
});
|
|
579
|
+
|
|
570
580
|
// Upload goods archive (auth via manifest uuid+emojicode)
|
|
571
581
|
app.post('/plugin/shoppe/upload', upload.single('archive'), async (req, res) => {
|
|
572
582
|
try {
|