@xuda.io/account_module 1.2.2284 → 1.2.2286
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 +238 -0
- package/index_ms.mjs +24 -0
- package/index_msa.mjs +24 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -67,6 +67,59 @@ const logs_msa = await import(`${module_path}/logs_module/index_msa.mjs`);
|
|
|
67
67
|
const ai_msa = await import(`${module_path}/ai_module/index_msa.mjs`);
|
|
68
68
|
const notification_msa = await import(`${module_path}/notification_module/index_msa.mjs`);
|
|
69
69
|
|
|
70
|
+
// ── Account main-project auto-naming ─────────────────────────────────────────
|
|
71
|
+
// The account's main project (is_account_project) is named after its owner:
|
|
72
|
+
// "John's Project" (personal) / "Acme Inc's Project" (business). It's set at
|
|
73
|
+
// creation (misc_module) and re-synced here whenever the account name changes.
|
|
74
|
+
// The name is display-only — everything functional references the project by id,
|
|
75
|
+
// and it's hidden from listings via is_account_project — so renaming is safe.
|
|
76
|
+
const ACCOUNT_PROJECT_LEGACY_RX = /^Account\s+\S+\s+main project$/i;
|
|
77
|
+
|
|
78
|
+
// Owner-derived project name, or null when the account has no name yet.
|
|
79
|
+
export const account_project_name = function (account_info) {
|
|
80
|
+
const ai = account_info || {};
|
|
81
|
+
let base = String((ai.account_type === 'business' ? ai.business_name : ai.first_name) || '').trim();
|
|
82
|
+
if (!base) return null;
|
|
83
|
+
base = base.charAt(0).toUpperCase() + base.slice(1); // capitalize the first letter for display ("john" → "John")
|
|
84
|
+
return `${base}'s Project`; // possessive: "John's", "James's" — always append 's
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// True when the project's current name is one WE generated (legacy sentinel, the
|
|
88
|
+
// pre-name fallback, or the name derived from ref_info) — so a sync/backfill may
|
|
89
|
+
// replace it. A name the user set themselves is NOT auto → never clobbered.
|
|
90
|
+
export const is_auto_account_project_name = function (name, ref_info) {
|
|
91
|
+
const n = String(name || '').trim();
|
|
92
|
+
if (!n) return true;
|
|
93
|
+
if (ACCOUNT_PROJECT_LEGACY_RX.test(n)) return true;
|
|
94
|
+
if (n === 'My Project') return true; // creation fallback before the name is known
|
|
95
|
+
const derived = account_project_name(ref_info);
|
|
96
|
+
return !!derived && n === derived;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Re-sync the account main project's name to the owner's CURRENT name. Only renames
|
|
100
|
+
// when the project still carries an auto-generated name (relative to ref_info = the
|
|
101
|
+
// name info BEFORE this change); a user-customized name is left untouched. Best-effort.
|
|
102
|
+
export const _sync_account_project_name = async function (account_obj, ref_info) {
|
|
103
|
+
try {
|
|
104
|
+
const proj_id = account_obj?.account_project_id;
|
|
105
|
+
if (!proj_id) return;
|
|
106
|
+
const new_name = account_project_name(account_obj.account_info);
|
|
107
|
+
if (!new_name) return;
|
|
108
|
+
const ret = await db_module.get_couch_doc('xuda_master', proj_id);
|
|
109
|
+
const proj = ret?.data;
|
|
110
|
+
if (!proj?._id || proj.app_name === new_name) return;
|
|
111
|
+
if (!is_auto_account_project_name(proj.app_name, ref_info)) return; // user-customized → keep
|
|
112
|
+
proj.app_name = new_name;
|
|
113
|
+
if (!proj.app_general_prop) proj.app_general_prop = {};
|
|
114
|
+
proj.app_general_prop.app_name = new_name;
|
|
115
|
+
proj.last_updated_ts = Date.now();
|
|
116
|
+
await db_module.save_app_obj(proj, proj_id);
|
|
117
|
+
console.log(`[account_project_name] ${account_obj._id}: renamed account project → "${new_name}"`);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.warn(`[account_project_name] sync failed for ${account_obj?._id}:`, err?.message || err);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
70
123
|
export const update_account_info = async function (req, job_id, headers) {
|
|
71
124
|
const { uid } = req;
|
|
72
125
|
const data = req;
|
|
@@ -92,6 +145,13 @@ export const update_account_info = async function (req, job_id, headers) {
|
|
|
92
145
|
const ret = await db_module.get_couch_doc('xuda_accounts', uid);
|
|
93
146
|
var account_obj = ret.data;
|
|
94
147
|
account_obj.ts = Date.now();
|
|
148
|
+
// Owner's name BEFORE this update — lets the project-name sync (after save) tell an
|
|
149
|
+
// auto-generated project name (safe to rename) from a user-customized one (keep).
|
|
150
|
+
const _old_name_info = {
|
|
151
|
+
first_name: account_obj.account_info?.first_name,
|
|
152
|
+
business_name: account_obj.account_info?.business_name,
|
|
153
|
+
account_type: account_obj.account_info?.account_type,
|
|
154
|
+
};
|
|
95
155
|
var change = '';
|
|
96
156
|
var account_info_changes_arr = [];
|
|
97
157
|
var error = {};
|
|
@@ -231,6 +291,13 @@ export const update_account_info = async function (req, job_id, headers) {
|
|
|
231
291
|
|
|
232
292
|
const save_ret = await db_module.save_couch_doc('xuda_accounts', account_obj);
|
|
233
293
|
|
|
294
|
+
// Keep the account main project's name in sync with the owner's name
|
|
295
|
+
// ("John's Project"). Only when a name field actually changed, and only if the
|
|
296
|
+
// project still has an auto-generated name (a user-customized one is left alone).
|
|
297
|
+
if (['first_name', 'business_name', 'account_type'].some((k) => account_info_changes_arr.includes(k))) {
|
|
298
|
+
await _sync_account_project_name(account_obj, _old_name_info);
|
|
299
|
+
}
|
|
300
|
+
|
|
234
301
|
if (account_obj.account_info?.profile_picture) {
|
|
235
302
|
if (!account_obj.account_info?.profile_avatar && account_obj.account_info.profile_avatar_stat !== 2) {
|
|
236
303
|
debugger;
|
|
@@ -1890,6 +1957,177 @@ export const ops_send_notification = async function (req = {}) {
|
|
|
1890
1957
|
} catch (err) { return { code: -1, data: err.message }; }
|
|
1891
1958
|
};
|
|
1892
1959
|
|
|
1960
|
+
// ---------------------------------------------------------------------------
|
|
1961
|
+
// Fleet server admin (superuser) — list the platform's own servers, queue a
|
|
1962
|
+
// soft/hard code deploy, and poll deploy status/progress. These are thin, SAFE
|
|
1963
|
+
// wrappers over the existing, battle-tested publish engine in misc_module
|
|
1964
|
+
// (_publish_rollout / _reinstall_server_detached): a deploy here only sets
|
|
1965
|
+
// stat:1 + publish_mode on the target's xuda_servers doc — the dev
|
|
1966
|
+
// orchestrator's 1-min tick (controller_module -> misc.publish_to_servers)
|
|
1967
|
+
// picks it up and runs the drain -> reinstall -> undrain rollout, writing
|
|
1968
|
+
// progress back onto the SAME doc (stat 1->2->3/4, stat_ts, publish_error). So
|
|
1969
|
+
// the blast radius of THIS module is a single CouchDB field write; the vetted
|
|
1970
|
+
// engine does the actual work. Deploy targets are region app-servers only
|
|
1971
|
+
// (docType 'regional_server' + application_server); farm/proxmox nodes and the
|
|
1972
|
+
// dev orchestrator itself are never deployable here.
|
|
1973
|
+
// ---------------------------------------------------------------------------
|
|
1974
|
+
|
|
1975
|
+
// stat state-machine on an xuda_servers doc -> friendly deploy state.
|
|
1976
|
+
const _SRV_DEPLOY_STATE = { 1: 'queued', 2: 'deploying', 3: 'idle', 4: 'failed' };
|
|
1977
|
+
const _srv_deploy_state = (doc) => _SRV_DEPLOY_STATE[doc && doc.stat] || 'unknown';
|
|
1978
|
+
const _srv_busy = (doc) => !!doc && (doc.stat === 1 || doc.stat === 2);
|
|
1979
|
+
// A deploy target is a region control-plane app-server (runs the /var/xuda
|
|
1980
|
+
// stack). NOT farm/proxmox nodes (no stack) and NOT dev (the orchestrator
|
|
1981
|
+
// itself — it deploys via rsync, and can't reinstall itself mid-rollout).
|
|
1982
|
+
const _srv_is_deployable = (doc) => !!(doc && doc.docType === 'regional_server' && doc.application_server === true && doc._id !== 'dev');
|
|
1983
|
+
const _srv_role = (doc) => {
|
|
1984
|
+
if (!doc) return 'other';
|
|
1985
|
+
if (doc.docType === 'regional_server' && doc.application_server) return 'region';
|
|
1986
|
+
if (doc.provider === 'proxmox' && doc.vmid) return 'farm_vm';
|
|
1987
|
+
if (doc.provider === 'proxmox' && (doc.node || doc.api_url)) return 'farm_node';
|
|
1988
|
+
return 'other';
|
|
1989
|
+
};
|
|
1990
|
+
|
|
1991
|
+
// Load every xuda_servers doc via _all_docs (nano .list) rather than a
|
|
1992
|
+
// match-all Mango selector — avoids the "no matching index" warning that would
|
|
1993
|
+
// otherwise spam the log on every (frequently-polled) status call.
|
|
1994
|
+
const _load_all_server_docs = async () => {
|
|
1995
|
+
const couch = await db_module.get_con('xuda_servers');
|
|
1996
|
+
const res = await couch.list({ include_docs: true });
|
|
1997
|
+
return (res.rows || []).map((r) => r.doc).filter((d) => d && !String(d._id).startsWith('_design'));
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
// Normalize one xuda_servers doc into the admin list view.
|
|
2001
|
+
const _srv_view = (doc) => {
|
|
2002
|
+
const info = doc.server_info && typeof doc.server_info === 'object' ? doc.server_info : {};
|
|
2003
|
+
return {
|
|
2004
|
+
id: doc._id,
|
|
2005
|
+
role: _srv_role(doc),
|
|
2006
|
+
deployable: _srv_is_deployable(doc),
|
|
2007
|
+
provider: doc.provider || (doc.droplet_id ? 'digitalocean' : doc.node || doc.api_url ? 'proxmox' : 'unknown'),
|
|
2008
|
+
hostname: doc.hostname || info.public_hostname || null,
|
|
2009
|
+
public_ip: doc.public || info.public_ip || null,
|
|
2010
|
+
private_ip: doc.private || info.private_ip || null, // WireGuard mesh IP (10.8.0.x)
|
|
2011
|
+
server_status: doc.server_status || info.status || 'unknown', // active | off
|
|
2012
|
+
server_is_full: !!doc.server_is_full,
|
|
2013
|
+
capacity: doc.server_capacity || null,
|
|
2014
|
+
countries: doc.countries || null,
|
|
2015
|
+
deploy: {
|
|
2016
|
+
state: _srv_deploy_state(doc),
|
|
2017
|
+
busy: _srv_busy(doc),
|
|
2018
|
+
publish_mode: doc.publish_mode || null,
|
|
2019
|
+
stat: typeof doc.stat === 'number' ? doc.stat : null,
|
|
2020
|
+
stat_ts: doc.stat_ts || null,
|
|
2021
|
+
error: doc.publish_error || null,
|
|
2022
|
+
requested_by: doc.deploy_requested_by || null,
|
|
2023
|
+
requested_ts: doc.deploy_requested_ts || null,
|
|
2024
|
+
},
|
|
2025
|
+
};
|
|
2026
|
+
};
|
|
2027
|
+
|
|
2028
|
+
// List every fleet server (regions + farm nodes) with live status + deploy
|
|
2029
|
+
// state. Read-only. Live status is maintained by controller_module.check_live_servers.
|
|
2030
|
+
export const ops_list_servers = async function (req = {}) {
|
|
2031
|
+
try {
|
|
2032
|
+
if (!_ops_is_super(req)) return { code: -403, data: 'superuser only' };
|
|
2033
|
+
const docs = await _load_all_server_docs();
|
|
2034
|
+
const rank = { region: 0, farm_node: 1, farm_vm: 2, other: 3 };
|
|
2035
|
+
const servers = docs
|
|
2036
|
+
.map(_srv_view)
|
|
2037
|
+
.sort((a, b) => rank[a.role] - rank[b.role] || (a.id === 'master' ? -1 : b.id === 'master' ? 1 : String(a.id).localeCompare(String(b.id))));
|
|
2038
|
+
return {
|
|
2039
|
+
code: 1,
|
|
2040
|
+
data: {
|
|
2041
|
+
servers,
|
|
2042
|
+
deploy_targets: servers.filter((s) => s.deployable).map((s) => s.id),
|
|
2043
|
+
orchestrator: 'dev.xuda.ai',
|
|
2044
|
+
note: 'Deploy runs from the dev orchestrator; region app-servers only. Soft = zero-downtime drain + reload; hard = teardown + clean install.',
|
|
2045
|
+
},
|
|
2046
|
+
};
|
|
2047
|
+
} catch (err) { return { code: -1, data: err.message }; }
|
|
2048
|
+
};
|
|
2049
|
+
|
|
2050
|
+
// Queue a code deploy to ONE region app-server. Enqueue-only + superuser-gated:
|
|
2051
|
+
// sets stat:1 + publish_mode on the target doc; the dev orchestrator rolls it
|
|
2052
|
+
// out. mode: 'soft' (default, zero-downtime) | 'hard' (teardown + clean
|
|
2053
|
+
// install). Guards: deployable target only; not the dev orchestrator; not
|
|
2054
|
+
// already busy; confirm required (hard requires the server id typed back).
|
|
2055
|
+
// dry_run returns the plan WITHOUT writing (safe preview).
|
|
2056
|
+
export const ops_deploy_server = async function (req = {}) {
|
|
2057
|
+
try {
|
|
2058
|
+
if (!_ops_is_super(req)) return { code: -403, data: 'superuser only' };
|
|
2059
|
+
const server_id = String(req.server_id || '').trim();
|
|
2060
|
+
const mode = req.mode === 'hard' ? 'hard' : 'soft';
|
|
2061
|
+
if (!server_id) return { code: -1, data: 'server_id is required' };
|
|
2062
|
+
if (server_id === 'dev') return { code: -1, data: 'the dev orchestrator deploys via rsync, not this rollout; pick a region (master/us/eu)' };
|
|
2063
|
+
|
|
2064
|
+
// FULL doc (no field projection) so it is safe to save back.
|
|
2065
|
+
const sr = await db_module.get_couch_doc('xuda_servers', server_id);
|
|
2066
|
+
if (sr.code < 0 || !sr.data) return { code: -1, data: `server "${server_id}" not found` };
|
|
2067
|
+
const doc = sr.data;
|
|
2068
|
+
if (!_srv_is_deployable(doc)) return { code: -1, data: `server "${server_id}" is not a deployable region app-server (needs docType 'regional_server' + application_server)` };
|
|
2069
|
+
|
|
2070
|
+
// Confirmation gate — proportional to blast radius.
|
|
2071
|
+
if (mode === 'hard') {
|
|
2072
|
+
if (req.confirm !== server_id) return { code: -1, data: `hard deploy tears down and reinstalls "${server_id}" (brief downtime). Re-send with confirm:"${server_id}" to proceed.` };
|
|
2073
|
+
} else {
|
|
2074
|
+
const ok = req.confirm === true || req.confirm === 'true' || req.confirm === server_id;
|
|
2075
|
+
if (!ok) return { code: -1, data: `confirm required: re-send with confirm:true to deploy "${server_id}" (soft, zero-downtime).` };
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
// Already-in-flight guard.
|
|
2079
|
+
if (_srv_busy(doc)) return { code: -1, data: `a deploy is already ${_srv_deploy_state(doc)} for "${server_id}"; wait for it to finish (poll ops_server_deploy_status).` };
|
|
2080
|
+
|
|
2081
|
+
const plan = { server_id, mode, current_state: _srv_deploy_state(doc), private_ip: doc.private || null };
|
|
2082
|
+
if (req.dry_run) return { code: 1, data: { dry_run: true, would_deploy: plan } };
|
|
2083
|
+
|
|
2084
|
+
// Enqueue: the dev orchestrator's 1-min publish tick picks stat:1 up.
|
|
2085
|
+
doc.stat = 1;
|
|
2086
|
+
doc.stat_ts = Date.now();
|
|
2087
|
+
doc.publish_mode = mode;
|
|
2088
|
+
doc.deploy_requested_by = req.uid;
|
|
2089
|
+
doc.deploy_requested_ts = Date.now();
|
|
2090
|
+
delete doc.publish_error;
|
|
2091
|
+
const wr = await db_module.save_couch_doc('xuda_servers', doc);
|
|
2092
|
+
if (wr.code < 0) return { code: -1, data: wr.data };
|
|
2093
|
+
|
|
2094
|
+
_ops_notify_ops(`Server deploy queued (${mode}): ${server_id}`, [['Server', server_id], ['Mode', mode], ['By', req.uid], ['State', 'queued'], ['Orchestrator', 'dev.xuda.ai']], 'OPS-DEPLOY-' + server_id);
|
|
2095
|
+
logs_msa.add_account_log({ uid: req.uid, method: 'ops_deploy_server', source: 'ops dashboard', response_status_code: 200, response_data: `queued ${mode} deploy for ${server_id} by ${req.uid}` });
|
|
2096
|
+
|
|
2097
|
+
return { code: 1, data: { enqueued: true, server_id, mode, state: 'queued', orchestrator: 'dev.xuda.ai', poll: 'ops_server_deploy_status' } };
|
|
2098
|
+
} catch (err) { return { code: -1, data: err.message }; }
|
|
2099
|
+
};
|
|
2100
|
+
|
|
2101
|
+
// Poll deploy status/progress for one server (or all region app-servers). Reads
|
|
2102
|
+
// the stat state-machine the rollout writes onto each xuda_servers doc.
|
|
2103
|
+
export const ops_server_deploy_status = async function (req = {}) {
|
|
2104
|
+
try {
|
|
2105
|
+
if (!_ops_is_super(req)) return { code: -403, data: 'superuser only' };
|
|
2106
|
+
const now = Date.now();
|
|
2107
|
+
const to_status = (doc) => ({
|
|
2108
|
+
id: doc._id,
|
|
2109
|
+
state: _srv_deploy_state(doc),
|
|
2110
|
+
busy: _srv_busy(doc),
|
|
2111
|
+
publish_mode: doc.publish_mode || null,
|
|
2112
|
+
stat: typeof doc.stat === 'number' ? doc.stat : null,
|
|
2113
|
+
stat_ts: doc.stat_ts || null,
|
|
2114
|
+
elapsed_ms: doc.stat_ts && _srv_busy(doc) ? now - doc.stat_ts : null,
|
|
2115
|
+
error: doc.publish_error || null,
|
|
2116
|
+
requested_by: doc.deploy_requested_by || null,
|
|
2117
|
+
requested_ts: doc.deploy_requested_ts || null,
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
if (req.server_id) {
|
|
2121
|
+
const sr = await db_module.get_couch_doc('xuda_servers', String(req.server_id).trim());
|
|
2122
|
+
if (sr.code < 0 || !sr.data) return { code: -1, data: `server "${req.server_id}" not found` };
|
|
2123
|
+
return { code: 1, data: to_status(sr.data) };
|
|
2124
|
+
}
|
|
2125
|
+
const docs = await _load_all_server_docs();
|
|
2126
|
+
const rows = docs.filter(_srv_is_deployable).map(to_status);
|
|
2127
|
+
return { code: 1, data: { servers: rows, any_busy: rows.some((r) => r.busy) } };
|
|
2128
|
+
} catch (err) { return { code: -1, data: err.message }; }
|
|
2129
|
+
};
|
|
2130
|
+
|
|
1893
2131
|
const TIPS_FILE_PATH = path.join(process.env.XUDA_HOME, 'dist', 'tips', 'tips.json');
|
|
1894
2132
|
let _did_you_know_tips_cache = null;
|
|
1895
2133
|
let _did_you_know_tips_cache_mtime = 0;
|
package/index_ms.mjs
CHANGED
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
export const account_project_name = async function (...args) {
|
|
21
|
+
return await broker.send_to_queue("account_project_name", ...args);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const is_auto_account_project_name = async function (...args) {
|
|
25
|
+
return await broker.send_to_queue("is_auto_account_project_name", ...args);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const _sync_account_project_name = async function (...args) {
|
|
29
|
+
return await broker.send_to_queue("_sync_account_project_name", ...args);
|
|
30
|
+
};
|
|
31
|
+
|
|
20
32
|
export const update_account_info = async function (...args) {
|
|
21
33
|
return await broker.send_to_queue("update_account_info", ...args);
|
|
22
34
|
};
|
|
@@ -177,6 +189,18 @@ export const ops_send_notification = async function (...args) {
|
|
|
177
189
|
return await broker.send_to_queue("ops_send_notification", ...args);
|
|
178
190
|
};
|
|
179
191
|
|
|
192
|
+
export const ops_list_servers = async function (...args) {
|
|
193
|
+
return await broker.send_to_queue("ops_list_servers", ...args);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const ops_deploy_server = async function (...args) {
|
|
197
|
+
return await broker.send_to_queue("ops_deploy_server", ...args);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const ops_server_deploy_status = async function (...args) {
|
|
201
|
+
return await broker.send_to_queue("ops_server_deploy_status", ...args);
|
|
202
|
+
};
|
|
203
|
+
|
|
180
204
|
export const did_you_know_tips = async function (...args) {
|
|
181
205
|
return await broker.send_to_queue("did_you_know_tips", ...args);
|
|
182
206
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
export const account_project_name = function (...args) {
|
|
21
|
+
broker.send_to_queue_async("account_project_name", ...args);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const is_auto_account_project_name = function (...args) {
|
|
25
|
+
broker.send_to_queue_async("is_auto_account_project_name", ...args);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const _sync_account_project_name = function (...args) {
|
|
29
|
+
broker.send_to_queue_async("_sync_account_project_name", ...args);
|
|
30
|
+
};
|
|
31
|
+
|
|
20
32
|
export const update_account_info = function (...args) {
|
|
21
33
|
broker.send_to_queue_async("update_account_info", ...args);
|
|
22
34
|
};
|
|
@@ -177,6 +189,18 @@ export const ops_send_notification = function (...args) {
|
|
|
177
189
|
broker.send_to_queue_async("ops_send_notification", ...args);
|
|
178
190
|
};
|
|
179
191
|
|
|
192
|
+
export const ops_list_servers = function (...args) {
|
|
193
|
+
broker.send_to_queue_async("ops_list_servers", ...args);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const ops_deploy_server = function (...args) {
|
|
197
|
+
broker.send_to_queue_async("ops_deploy_server", ...args);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const ops_server_deploy_status = function (...args) {
|
|
201
|
+
broker.send_to_queue_async("ops_server_deploy_status", ...args);
|
|
202
|
+
};
|
|
203
|
+
|
|
180
204
|
export const did_you_know_tips = function (...args) {
|
|
181
205
|
broker.send_to_queue_async("did_you_know_tips", ...args);
|
|
182
206
|
};
|