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