@xuda.io/account_module 1.2.2273 → 1.2.2275
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/index.mjs +108 -3
- package/index_ms.mjs +4 -0
- package/index_msa.mjs +4 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1415,7 +1415,30 @@ export const verify_account = async function (req) {
|
|
|
1415
1415
|
if (ret.code < 0) {
|
|
1416
1416
|
return ret_acc;
|
|
1417
1417
|
}
|
|
1418
|
-
|
|
1418
|
+
|
|
1419
|
+
// Retire the "Confirm your email" banner notification (created at signup)
|
|
1420
|
+
// now that the account is verified: mark it read and push the updated doc
|
|
1421
|
+
// over WS — the dashboard's SAVE_notification mutation removes read docs
|
|
1422
|
+
// live, so any open tab drops the banner without a refresh. Non-fatal.
|
|
1423
|
+
try {
|
|
1424
|
+
const n_ret = await db_module.find_couch_query('xuda_notification', {
|
|
1425
|
+
selector: { uid: account_id, topic: 'confirm_email', read: false },
|
|
1426
|
+
limit: 10,
|
|
1427
|
+
});
|
|
1428
|
+
for (const n_doc of n_ret?.data?.docs || n_ret?.docs || []) {
|
|
1429
|
+
n_doc.read = true;
|
|
1430
|
+
n_doc.read_ts = Date.now();
|
|
1431
|
+
n_doc.read_note = 'account verified';
|
|
1432
|
+
await db_module.save_couch_doc('xuda_notification', n_doc);
|
|
1433
|
+
ws_dashboard_msa.notification({ to: account_id, data: n_doc });
|
|
1434
|
+
}
|
|
1435
|
+
} catch (e) {
|
|
1436
|
+
console.warn('[verify_account] confirm_email banner cleanup failed:', e.message);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// is_boarded rides along so the verify route can tell the client whether
|
|
1440
|
+
// onboarding is still pending (it is, for a fresh email signup).
|
|
1441
|
+
return { code: 1, data: { ...ret_acc.data, is_boarded: !!obj.isBoarded } };
|
|
1419
1442
|
};
|
|
1420
1443
|
|
|
1421
1444
|
export const validate_user_plan = async function (req) {
|
|
@@ -1512,6 +1535,7 @@ export const get_cpu = async function (req) {
|
|
|
1512
1535
|
};
|
|
1513
1536
|
|
|
1514
1537
|
const get_account_log_object = function (body, status, service, source, response_data, ip, headers, security) {
|
|
1538
|
+
const is_key = !!(body.api_key || body.api_pk || body.api_sk);
|
|
1515
1539
|
return {
|
|
1516
1540
|
uid: body.uid,
|
|
1517
1541
|
ip,
|
|
@@ -1521,7 +1545,12 @@ const get_account_log_object = function (body, status, service, source, response
|
|
|
1521
1545
|
security: _conf.cpi_methods?.[service]?.log,
|
|
1522
1546
|
api_pk: body.api_pk,
|
|
1523
1547
|
api_sk: body.api_sk,
|
|
1524
|
-
|
|
1548
|
+
// Developer API keys (xuda_api_keys) ride req.api_key; `channel` says HOW
|
|
1549
|
+
// the call arrived — 'mcp' (tagged by mcp_module's executor via the
|
|
1550
|
+
// xu-channel header), 'api' (a key over plain REST) or 'dashboard'.
|
|
1551
|
+
api_key: body.api_key,
|
|
1552
|
+
channel: headers?.['xu-channel'] === 'mcp' ? 'mcp' : is_key ? 'api' : 'dashboard',
|
|
1553
|
+
dashboard: !is_key,
|
|
1525
1554
|
response_status_code: status,
|
|
1526
1555
|
response_data,
|
|
1527
1556
|
source,
|
|
@@ -1538,7 +1567,10 @@ export const add_account_log_util = async function (body, status, service, sourc
|
|
|
1538
1567
|
if (_conf.cpi_methods?.[service]?.private) return;
|
|
1539
1568
|
const security = _conf.cpi_methods?.[service].security;
|
|
1540
1569
|
if (!security) {
|
|
1541
|
-
|
|
1570
|
+
// Interactive dashboard reads are noise, but reads made with a developer
|
|
1571
|
+
// key (api_key / api_pk / api_sk) ARE the product — log them so the
|
|
1572
|
+
// Developers overview reflects real API/MCP traffic.
|
|
1573
|
+
if (service.substr(0, 4) === 'get_' && !body.api_pk && !body.api_sk && !body.api_key) return;
|
|
1542
1574
|
}
|
|
1543
1575
|
|
|
1544
1576
|
logs_msa.add_account_log(get_account_log_object(body, status, service, source, response_data, ip, headers, security));
|
|
@@ -2588,6 +2620,79 @@ export const maybe_send_welcome_email = async function (uid) {
|
|
|
2588
2620
|
}
|
|
2589
2621
|
};
|
|
2590
2622
|
|
|
2623
|
+
// Every-3-days verification nudge for accounts still at stat 1 (unverified).
|
|
2624
|
+
// Runs from the controller's daily cron (master/dev only); the daily tick plus
|
|
2625
|
+
// the per-account 3-day gate below yields the every-3-days cadence. Each email
|
|
2626
|
+
// carries a rotating did-you-know card. Capped at MAX_VERIFICATION_REMINDERS
|
|
2627
|
+
// per account. `req` may override the knobs for testing:
|
|
2628
|
+
// { min_age_ms, interval_ms, max, limit, uids: ['acc_…'] }
|
|
2629
|
+
// (`uids` restricts the sweep to specific accounts — for E2E tests.)
|
|
2630
|
+
export const send_verification_reminders = async function (req) {
|
|
2631
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
2632
|
+
const MIN_AGE_MS = req?.min_age_ms ?? 3 * DAY_MS; // don't nudge before 3 days old
|
|
2633
|
+
const INTERVAL_MS = req?.interval_ms ?? 3 * DAY_MS; // and at most every 3 days
|
|
2634
|
+
const MAX_VERIFICATION_REMINDERS = req?.max ?? 5;
|
|
2635
|
+
try {
|
|
2636
|
+
const selector = { stat: 1, docType: 'account' };
|
|
2637
|
+
if (Array.isArray(req?.uids) && req.uids.length) selector._id = { $in: req.uids };
|
|
2638
|
+
const find_ret = await db_module.find_couch_query('xuda_accounts', {
|
|
2639
|
+
selector,
|
|
2640
|
+
fields: ['_id', 'date_created_ts', 'verification_reminder_last_ts', 'verification_reminder_count', 'account_info.email'],
|
|
2641
|
+
limit: req?.limit ?? 99999,
|
|
2642
|
+
});
|
|
2643
|
+
const rows = find_ret?.data?.docs || find_ret?.docs || [];
|
|
2644
|
+
const now = Date.now();
|
|
2645
|
+
const host = _conf.is_debug ? process.env.XUDA_HOSTNAME || _conf.domain : _conf.domain;
|
|
2646
|
+
const base = `https://${host}`;
|
|
2647
|
+
const tips = load_did_you_know_tips();
|
|
2648
|
+
let sent = 0;
|
|
2649
|
+
|
|
2650
|
+
for (const row of rows) {
|
|
2651
|
+
const uid = row._id;
|
|
2652
|
+
const count = row.verification_reminder_count || 0;
|
|
2653
|
+
if (count >= MAX_VERIFICATION_REMINDERS) continue;
|
|
2654
|
+
if (!row.account_info?.email) continue;
|
|
2655
|
+
const age = now - (row.date_created_ts || 0);
|
|
2656
|
+
if (!row.date_created_ts || age < MIN_AGE_MS) continue;
|
|
2657
|
+
if (row.verification_reminder_last_ts && now - row.verification_reminder_last_ts < INTERVAL_MS) continue;
|
|
2658
|
+
|
|
2659
|
+
// Rotate the card: offset by a uid hash so users don't all get the same
|
|
2660
|
+
// tip, advance by count so each reminder shows a fresh one.
|
|
2661
|
+
let card = { title: 'Did you know?', text: 'Xuda is an all-in-one AI platform — chats, apps, websites, hosting and more.', image: '', url: `${base}/dashboard` };
|
|
2662
|
+
if (tips.length) {
|
|
2663
|
+
const uid_hash = String(uid).split('').reduce((a, c) => a + c.charCodeAt(0), 0);
|
|
2664
|
+
const tip = tips[(uid_hash + count) % tips.length];
|
|
2665
|
+
card = { title: tip.title, text: tip.text, image: tip.image ? `${base}${tip.image}` : '', url: tip.url || `${base}/dashboard` };
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
await notification_msa.submit_notification({
|
|
2669
|
+
type: 'account',
|
|
2670
|
+
app_id: null,
|
|
2671
|
+
uid_arr: [uid],
|
|
2672
|
+
topic: 'verification_reminder',
|
|
2673
|
+
params: { hostname: host, ref: uid, card_title: card.title, card_text: card.text, card_image: card.image, card_url: card.url },
|
|
2674
|
+
ref: null,
|
|
2675
|
+
email: null,
|
|
2676
|
+
});
|
|
2677
|
+
|
|
2678
|
+
// Persist the cadence state on the account doc.
|
|
2679
|
+
const account_doc = await db_module.get_couch_doc_native('xuda_accounts', uid);
|
|
2680
|
+
if (account_doc) {
|
|
2681
|
+
account_doc.verification_reminder_last_ts = now;
|
|
2682
|
+
account_doc.verification_reminder_count = count + 1;
|
|
2683
|
+
await db_module.save_couch_doc('xuda_accounts', account_doc);
|
|
2684
|
+
}
|
|
2685
|
+
sent++;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
console.log(`[send_verification_reminders] scanned ${rows.length} unverified accounts, sent ${sent}`);
|
|
2689
|
+
return { code: 1, data: { scanned: rows.length, sent } };
|
|
2690
|
+
} catch (err) {
|
|
2691
|
+
console.error('[send_verification_reminders]', err.message);
|
|
2692
|
+
return { code: -1, data: err.message };
|
|
2693
|
+
}
|
|
2694
|
+
};
|
|
2695
|
+
|
|
2591
2696
|
// Resolve an account uid from a public username (account_info.username).
|
|
2592
2697
|
// Used by the public-profile route so /public_profiles/<username> resolves.
|
|
2593
2698
|
export const get_uid_by_username = async function (username) {
|
package/index_ms.mjs
CHANGED
|
@@ -209,6 +209,10 @@ export const maybe_send_welcome_email = async function (...args) {
|
|
|
209
209
|
return await broker.send_to_queue("maybe_send_welcome_email", ...args);
|
|
210
210
|
};
|
|
211
211
|
|
|
212
|
+
export const send_verification_reminders = async function (...args) {
|
|
213
|
+
return await broker.send_to_queue("send_verification_reminders", ...args);
|
|
214
|
+
};
|
|
215
|
+
|
|
212
216
|
export const get_uid_by_username = async function (...args) {
|
|
213
217
|
return await broker.send_to_queue("get_uid_by_username", ...args);
|
|
214
218
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -209,6 +209,10 @@ export const maybe_send_welcome_email = function (...args) {
|
|
|
209
209
|
broker.send_to_queue_async("maybe_send_welcome_email", ...args);
|
|
210
210
|
};
|
|
211
211
|
|
|
212
|
+
export const send_verification_reminders = function (...args) {
|
|
213
|
+
broker.send_to_queue_async("send_verification_reminders", ...args);
|
|
214
|
+
};
|
|
215
|
+
|
|
212
216
|
export const get_uid_by_username = function (...args) {
|
|
213
217
|
broker.send_to_queue_async("get_uid_by_username", ...args);
|
|
214
218
|
};
|