wiki-plugin-salon 0.0.2 → 0.0.4
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/package.json +2 -2
- package/server/server.js +39 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-plugin-salon",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Federated Wiki plugin — community gathering space with self-registration and plugin event feeds",
|
|
5
5
|
"keywords": ["wiki", "plugin", "salon", "community", "planet-nine"],
|
|
6
6
|
"author": "Planet Nine",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"type": "commonjs",
|
|
9
9
|
"main": "index.js",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"
|
|
11
|
+
"node-fetch": "^2.6.1"
|
|
12
12
|
}
|
|
13
13
|
}
|
package/server/server.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const crypto = require('crypto');
|
|
7
|
+
const fetch = require('node-fetch');
|
|
7
8
|
|
|
8
9
|
// ── Storage paths ─────────────────────────────────────────────────────────────
|
|
9
10
|
|
|
@@ -93,14 +94,21 @@ function buildEventEmail(config, event, detail) {
|
|
|
93
94
|
|
|
94
95
|
// ── Email ─────────────────────────────────────────────────────────────────────
|
|
95
96
|
|
|
96
|
-
function
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
function getMinnieUrl(config) {
|
|
98
|
+
const base = config.allyabaseUrl ||
|
|
99
|
+
(config.joanUrl ? config.joanUrl.replace(/\/plugin\/allyabase\/joan$/, '') : '');
|
|
100
|
+
return base ? base.replace(/\/$/, '') + '/plugin/allyabase/minnie' : null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function sendViaMinnie(minnieUrl, { from, to, subject, text }) {
|
|
104
|
+
const resp = await fetch(`${minnieUrl}/send`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({ from, to, subject, text }),
|
|
108
|
+
timeout: 10000
|
|
103
109
|
});
|
|
110
|
+
if (!resp.ok) throw new Error(`Minnie /send failed: ${resp.status}`);
|
|
111
|
+
return resp.json();
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
async function notifyMembers(config, subject, text) {
|
|
@@ -108,12 +116,17 @@ async function notifyMembers(config, subject, text) {
|
|
|
108
116
|
const recipients = Object.values(tenants).filter(t => t.status === 'active' && t.email);
|
|
109
117
|
if (recipients.length === 0) return;
|
|
110
118
|
|
|
111
|
-
const
|
|
112
|
-
|
|
119
|
+
const minnieUrl = getMinnieUrl(config);
|
|
120
|
+
if (!minnieUrl) {
|
|
121
|
+
console.warn('salon: no Minnie URL configured — skipping email notifications');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const from = config.fromEmail || 'salon@planetnine.app';
|
|
113
126
|
|
|
114
127
|
for (const tenant of recipients) {
|
|
115
128
|
try {
|
|
116
|
-
await
|
|
129
|
+
await sendViaMinnie(minnieUrl, { from, to: tenant.email, subject, text });
|
|
117
130
|
} catch (err) {
|
|
118
131
|
console.error(`salon: email failed for ${tenant.email}:`, err.message);
|
|
119
132
|
}
|
|
@@ -632,15 +645,12 @@ function generateAdminPage(config, tenants) {
|
|
|
632
645
|
<label>From address</label>
|
|
633
646
|
<input name="fromEmail" value="${esc(config.fromEmail || 'salon@planetnine.app')}" placeholder="salon@planetnine.app">
|
|
634
647
|
|
|
635
|
-
<label>Minnie host</label>
|
|
636
|
-
<input name="minnieHost" value="${esc(String(config.minnieHost || 'localhost'))}" placeholder="localhost">
|
|
637
|
-
|
|
638
|
-
<label>Minnie port</label>
|
|
639
|
-
<input name="minniePort" type="number" value="${esc(String(config.minniePort || 2525))}" placeholder="2525">
|
|
640
|
-
|
|
641
648
|
<label>Allyabase URL</label>
|
|
642
649
|
<input name="allyabaseUrl" value="${esc(config.allyabaseUrl || (config.joanUrl ? config.joanUrl.replace(/\/plugin\/allyabase\/joan$/, '') : ''))}" placeholder="https://dev.allyabase.com">
|
|
643
|
-
<div class="form-hint">
|
|
650
|
+
<div class="form-hint">
|
|
651
|
+
Emails sent via <code>${esc((config.allyabaseUrl || (config.joanUrl ? config.joanUrl.replace(/\/plugin\/allyabase\/joan$/, '') : 'https://dev.allyabase.com')) + '/plugin/allyabase/minnie')}</code><br>
|
|
652
|
+
OTP codes sent via <code>${esc(config.joanUrl || 'https://dev.allyabase.com/plugin/allyabase/joan')}</code>
|
|
653
|
+
</div>
|
|
644
654
|
|
|
645
655
|
<div style="margin-top:16px;">
|
|
646
656
|
<button type="submit" class="btn btn-primary btn-sm">Save Settings</button>
|
|
@@ -1166,14 +1176,12 @@ function startServer(params) {
|
|
|
1166
1176
|
|
|
1167
1177
|
app.post('/plugin/salon/config', owner, json, (req, res) => {
|
|
1168
1178
|
const config = loadConfig();
|
|
1169
|
-
const { registrationMode, title, description, fromEmail,
|
|
1179
|
+
const { registrationMode, title, description, fromEmail, joanUrl, allyabaseUrl } = req.body;
|
|
1170
1180
|
const modes = ['open', 'closed', 'grant'];
|
|
1171
1181
|
if (registrationMode && modes.includes(registrationMode)) config.registrationMode = registrationMode;
|
|
1172
1182
|
if (title && title.trim()) config.title = title.trim();
|
|
1173
1183
|
if (description !== undefined) config.description = description.trim();
|
|
1174
1184
|
if (fromEmail !== undefined) config.fromEmail = fromEmail.trim();
|
|
1175
|
-
if (minnieHost !== undefined) config.minnieHost = minnieHost.trim();
|
|
1176
|
-
if (minniePort !== undefined) config.minniePort = parseInt(minniePort) || 2525;
|
|
1177
1185
|
if (joanUrl !== undefined) config.joanUrl = joanUrl.trim();
|
|
1178
1186
|
if (allyabaseUrl !== undefined && allyabaseUrl.trim()) {
|
|
1179
1187
|
config.allyabaseUrl = allyabaseUrl.trim().replace(/\/$/, '');
|
|
@@ -1427,16 +1435,21 @@ function startServer(params) {
|
|
|
1427
1435
|
const recipients = Object.values(tenants).filter(t => t.status === 'active' && t.email);
|
|
1428
1436
|
|
|
1429
1437
|
if (recipients.length === 0) {
|
|
1430
|
-
return res.json({ success: true, sent: 0 });
|
|
1438
|
+
return res.json({ success: true, sent: 0, warning: 'No active members with email addresses found' });
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
const minnieUrl = getMinnieUrl(config);
|
|
1442
|
+
if (!minnieUrl) {
|
|
1443
|
+
return res.status(503).json({ error: 'Email not configured — save your Allyabase URL in settings first' });
|
|
1431
1444
|
}
|
|
1432
1445
|
|
|
1433
|
-
const
|
|
1434
|
-
const from = `"${config.title}" <${config.fromEmail || 'salon@planetnine.app'}>`;
|
|
1446
|
+
const from = config.fromEmail || 'salon@planetnine.app';
|
|
1435
1447
|
let sent = 0;
|
|
1448
|
+
const errors = [];
|
|
1436
1449
|
|
|
1437
1450
|
for (const tenant of recipients) {
|
|
1438
1451
|
try {
|
|
1439
|
-
await
|
|
1452
|
+
await sendViaMinnie(minnieUrl, {
|
|
1440
1453
|
from,
|
|
1441
1454
|
to: tenant.email,
|
|
1442
1455
|
subject: subject.slice(0, 200),
|
|
@@ -1445,10 +1458,11 @@ function startServer(params) {
|
|
|
1445
1458
|
sent++;
|
|
1446
1459
|
} catch (err) {
|
|
1447
1460
|
console.error(`salon: announce email failed for ${tenant.email}:`, err.message);
|
|
1461
|
+
errors.push(tenant.email);
|
|
1448
1462
|
}
|
|
1449
1463
|
}
|
|
1450
1464
|
|
|
1451
|
-
res.json({ success: true, sent });
|
|
1465
|
+
res.json({ success: true, sent, ...(errors.length && { errors }) });
|
|
1452
1466
|
});
|
|
1453
1467
|
|
|
1454
1468
|
// ── Freyja federation page ──────────────────────────────────────────────────
|