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.
Files changed (2) hide show
  1. package/package.json +2 -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.2",
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
- "nodemailer": "^6.9.0"
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 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 }
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 transporter = createTransporter(config);
112
- const from = `"${config.title}" <${config.fromEmail || 'salon@planetnine.app'}>`;
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 transporter.sendMail({ from, to: tenant.email, subject, text });
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">Joan OTP codes are sent via <code>${esc(config.joanUrl || 'https://dev.allyabase.com/plugin/allyabase/joan')}</code></div>
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, minnieHost, minniePort, joanUrl, allyabaseUrl } = req.body;
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 transporter = createTransporter(config);
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 transporter.sendMail({
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 ──────────────────────────────────────────────────