@xuda.io/account_module 1.2.2295 → 1.2.2296

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 CHANGED
@@ -1999,10 +1999,10 @@ export const ops_account_snapshot = async function (req = {}) {
1999
1999
  const a = ar.data;
2000
2000
  let credits = { max: 0, used: 0, remaining: 0 };
2001
2001
  try {
2002
- const cr = await get_account_ai_usage({ uid: account_uid, ..._LIFETIME_WINDOW });
2002
+ const cr = await get_account_ai_usage({ uid: account_uid });
2003
2003
  if (cr && cr.code === 55) {
2004
2004
  const max = Math.round((cr.data?.credits?.total || 0) * 100) / 100;
2005
- const used = Math.round(_usage_total(cr.data?.usage) * 100) / 100;
2005
+ const used = Math.round((cr.data?.usage?.total || 0) * 100) / 100;
2006
2006
  credits = { max, used, remaining: Math.round((max - used) * 100) / 100, by_source: cr.data?.credits || {} };
2007
2007
  }
2008
2008
  } catch (e) { console.error('[ops snapshot credits]', e.message); }
@@ -2289,6 +2289,11 @@ export const ops_seed_do_datacenters = async function (req = {}) {
2289
2289
  docType: 'do_datacenter',
2290
2290
  provider: 'digitalocean',
2291
2291
  is_do_datacenter: true,
2292
+ // Inventory doc, NOT a live controller: keep the server-health check from
2293
+ // probing https://do_<slug>.xuda.ai/cpi/... (there is no such endpoint - DNS
2294
+ // fails). The health loop skips any doc with application_server === false
2295
+ // (controller_module codex_check_xuda_servers_health), same as farm nodes.
2296
+ application_server: false,
2292
2297
  region_slug: dc.slug, // DO createDroplet region
2293
2298
  location: dc.slug, // strips (\d+$) to the metro for macro scoping, like a farm node's hel1
2294
2299
  metro: dc.metro,
@@ -6466,13 +6471,11 @@ export const save_cache_hit = async function (_id) {
6466
6471
  ///////////////////////////////
6467
6472
 
6468
6473
  export const get_account_ai_usage = async function (req, job_id, headers) {
6469
- // Default to the LIFETIME window (all-time spend) — the credit model is a
6470
- // remaining-pool ledger (see _LIFETIME_WINDOW + the Credit Management page).
6471
- // Callers that want a specific range (the usage chart in WorkspaceUsage.vue)
6472
- // pass explicit year/month/day. The nav meter + ring call with no window, so
6473
- // this makes them read lifetime "used" and match the Credit Management page
6474
- // instead of a current-month slice against a lifetime pool.
6475
- const { uid, year_from = 2000, month_from = 1, day_from = 1, year_to = 2999, month_to = 12, day_to = 31 } = req;
6474
+ const d = new Date();
6475
+ const curr_month = Number(String(d.getMonth() + 1).padStart(2, '0')); // months are 0-based
6476
+ const curr_year = d.getFullYear();
6477
+
6478
+ const { uid, year_from = curr_year, month_from = curr_month, day_from = 0, year_to = curr_year, month_to = curr_month, day_to = 99 } = req;
6476
6479
  try {
6477
6480
  const app_id = await get_account_default_project_id(uid);
6478
6481
 
@@ -6496,13 +6499,8 @@ export const get_account_ai_usage = async function (req, job_id, headers) {
6496
6499
 
6497
6500
  // console.log('ai_usage', ai_usage.rows);
6498
6501
 
6502
+ let total_usage = response.total_usage;
6499
6503
  let profile = response.profiles;
6500
- // The ai_usage2 list function emits per-profile spend but sometimes a null
6501
- // rolled-up total. Roll it up HERE at the source so every consumer gets a real
6502
- // usage.total — including the dashboard nav (shared-store fetchCredits reads
6503
- // data.usage.total directly). Without this, Number(null) -> 0 hid all spend in
6504
- // the meter/ring and disabled the account-wide credit cap.
6505
- let total_usage = _usage_total({ total: response.total_usage, profile });
6506
6504
 
6507
6505
  const ai_credits = await db_module.get_couch_view('xuda_billing', 'ai_credits', {
6508
6506
  startkey: [uid, ''],
@@ -6612,23 +6610,6 @@ const _fold_usage = function (profile_map) {
6612
6610
  return { by_profile, by_user };
6613
6611
  };
6614
6612
 
6615
- // Single source of truth for an account's rolled-up AI-credit usage. The usage
6616
- // view reliably emits per-profile spend but sometimes returns a null rolled-up
6617
- // total (usage.total); Number(null)||0 then collapsed "used" to 0 EVERYWHERE —
6618
- // hiding spend in the nav/meter AND making the account-wide credit cap never
6619
- // fire. Prefer the view's own total when finite, otherwise sum the per-profile
6620
- // fold (same source, same credit unit).
6621
- const _usage_total = function (usage_data) {
6622
- // NOTE: Number(null) === 0 (and IS finite), which is exactly what collapsed
6623
- // "used" to 0 — so guard on the RAW type, not the coerced number. Only trust
6624
- // the view's own total when it is a real finite number; otherwise roll up the
6625
- // per-profile fold.
6626
- const t = usage_data?.total;
6627
- if (typeof t === 'number' && Number.isFinite(t)) return t;
6628
- const { by_profile } = _fold_usage(usage_data?.profile);
6629
- return Object.values(by_profile).reduce((a, b) => a + (Number(b) || 0), 0);
6630
- };
6631
-
6632
6613
  // Wide window so "used" reads as all-time spend (the lifetime / remaining-pool model).
6633
6614
  const _LIFETIME_WINDOW = { year_from: 2000, month_from: 1, day_from: 1, year_to: 2999, month_to: 12, day_to: 31 };
6634
6615
 
@@ -6644,8 +6625,8 @@ const _get_scoped_usage = async function (owner_uid, need_raw) {
6644
6625
  if (cached && now - cached.ts < _SCOPED_USAGE_TTL && (!need_raw || cached.has_raw)) return cached.data;
6645
6626
 
6646
6627
  const usage_ret = await get_account_ai_usage({ uid: owner_uid, ..._LIFETIME_WINDOW });
6628
+ const total = Number(usage_ret?.data?.usage?.total) || 0;
6647
6629
  const { by_profile, by_user } = _fold_usage(usage_ret?.data?.usage?.profile);
6648
- const total = _usage_total(usage_ret?.data?.usage);
6649
6630
 
6650
6631
  let by_model = {};
6651
6632
  let by_source = {};
@@ -6776,8 +6757,8 @@ export const evaluate_credit_gate = async function (req) {
6776
6757
 
6777
6758
  // Back-compat: no rules or disabled => legacy single ledger cap, byte-for-byte.
6778
6759
  if (!rules || rules.enabled === false) {
6779
- const usage = await get_account_ai_usage({ uid: owner, ..._LIFETIME_WINDOW });
6780
- const over = (Number(usage?.data?.credits?.total) || 0) - _usage_total(usage?.data?.usage) < -0.5;
6760
+ const usage = await get_account_ai_usage({ uid: owner });
6761
+ const over = (Number(usage?.data?.credits?.total) || 0) - (Number(usage?.data?.usage?.total) || 0) < -0.5;
6781
6762
  return { block: over, scope: over ? 'pool' : null, reason: over ? _SCOPE_REASON.ledger : '', hard_breached: over ? [{ scope: 'pool', key: 'ledger' }] : [], soft_breached: [], account_id: owner };
6782
6763
  }
6783
6764
 
@@ -7124,13 +7105,10 @@ export const broadcast_credits = function (uid) {
7124
7105
  _credits_broadcast_timers[uid] = setTimeout(async () => {
7125
7106
  delete _credits_broadcast_timers[uid];
7126
7107
  try {
7127
- // Lifetime window (matches the Credit Management meter) + the same null-total
7128
- // rollup as _get_scoped_usage — get_account_ai_usage can return a null
7129
- // rolled-up usage.total, which otherwise broadcasts used:0 to the live nav.
7130
- const ret = await get_account_ai_usage({ uid, ..._LIFETIME_WINDOW });
7108
+ const ret = await get_account_ai_usage({ uid });
7131
7109
  if (!ret || ret.code !== 55) return;
7132
7110
  const max = Math.round((ret.data?.credits?.total || 0) * 100) / 100;
7133
- const used = Math.round(_usage_total(ret.data?.usage) * 100) / 100;
7111
+ const used = Math.round((ret.data?.usage?.total || 0) * 100) / 100;
7134
7112
  ws_dashboard_msa.emit_message_to_dashboard({
7135
7113
  service: 'credits_update',
7136
7114
  to: [uid],
@@ -7545,6 +7523,30 @@ const _nl_one = async (db, docType) => {
7545
7523
  }
7546
7524
  };
7547
7525
 
7526
+ // The newsletter/content list + random-pick queries above all select on a single
7527
+ // `docType` field. Without an index Couch full-scans each content DB and logs
7528
+ // "No matching index found". These DBs live locally on dev + master (regions read
7529
+ // newsletter from master via DSN), so ensure a [docType] index on each at module
7530
+ // load, idempotent, only on the content-home nodes.
7531
+ (async () => {
7532
+ const host = process.env.XUDA_HOSTNAME;
7533
+ if (host !== 'dev.xuda.ai' && host !== 'master.xuda.ai') return;
7534
+ const content_dbs = ['xuda_newsletter', 'xuda_dev_articles', 'xuda_news_articles', 'xuda_did_you_know'];
7535
+ for (const db of content_dbs) {
7536
+ try {
7537
+ const ret = await db_module.create_couch_index(db, {
7538
+ index: { fields: ['docType'] },
7539
+ name: 'idx_content_docType',
7540
+ ddoc: 'idx_content_docType',
7541
+ type: 'json',
7542
+ });
7543
+ if (ret?.error) console.error(`[newsletter] ${db} docType index create failed:`, ret.error);
7544
+ } catch (err) {
7545
+ console.error(`[newsletter] ${db} docType index init failed:`, err?.message || err);
7546
+ }
7547
+ }
7548
+ })();
7549
+
7548
7550
  const _nl_fashion_url = (p) => {
7549
7551
  const rel = p.custom_url || (p.slug ? '/' + p.slug : '');
7550
7552
  return `https://xuda.fashion${rel}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/account_module",
3
- "version": "1.2.2295",
3
+ "version": "1.2.2296",
4
4
  "description": "Xuda Account Server Module",
5
5
  "main": "index.mjs",
6
6
  "dependencies": {
package/download_model.js DELETED
@@ -1,56 +0,0 @@
1
- // download-models.js
2
- const https = require('https');
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const models = ['u2net.onnx', 'u2net.onnx.bin', 'u2net.onnx.wasm'];
7
-
8
- const baseUrl = 'https://static.imgly.com/background-removal/models/medium/';
9
- const outputDir = '/var/xuda/assets/imgly-bg';
10
-
11
- // Create directory if it doesn't exist
12
- if (!fs.existsSync(outputDir)) {
13
- fs.mkdirSync(outputDir, { recursive: true });
14
- }
15
-
16
- async function downloadFile(url, filepath) {
17
- return new Promise((resolve, reject) => {
18
- const file = fs.createWriteStream(filepath);
19
- https
20
- .get(url, (response) => {
21
- if (response.statusCode === 200) {
22
- response.pipe(file);
23
- file.on('finish', () => {
24
- file.close();
25
- console.log(`Downloaded: ${path.basename(filepath)}`);
26
- resolve();
27
- });
28
- } else {
29
- reject(new Error(`HTTP ${response.statusCode}: ${url}`));
30
- }
31
- })
32
- .on('error', (err) => {
33
- fs.unlink(filepath, () => {}); // Delete partial file
34
- reject(err);
35
- });
36
- });
37
- }
38
-
39
- async function downloadAll() {
40
- console.log('Downloading models to:', outputDir);
41
-
42
- for (const model of models) {
43
- const url = baseUrl + model;
44
- const filepath = path.join(outputDir, model);
45
-
46
- try {
47
- await downloadFile(url, filepath);
48
- } catch (error) {
49
- console.error(`Failed to download ${model}:`, error.message);
50
- }
51
- }
52
-
53
- console.log('Download complete!');
54
- }
55
-
56
- downloadAll();
@@ -1,56 +0,0 @@
1
- // download-models.js
2
- const https = require('https');
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const models = ['u2net.onnx', 'u2net.onnx.bin', 'u2net.onnx.wasm'];
7
-
8
- const baseUrl = 'https://static.imgly.com/background-removal/models/medium/';
9
- const outputDir = '/var/xuda/assets/imgly-bg';
10
-
11
- // Create directory if it doesn't exist
12
- if (!fs.existsSync(outputDir)) {
13
- fs.mkdirSync(outputDir, { recursive: true });
14
- }
15
-
16
- async function downloadFile(url, filepath) {
17
- return new Promise((resolve, reject) => {
18
- const file = fs.createWriteStream(filepath);
19
- https
20
- .get(url, (response) => {
21
- if (response.statusCode === 200) {
22
- response.pipe(file);
23
- file.on('finish', () => {
24
- file.close();
25
- console.log(`Downloaded: ${path.basename(filepath)}`);
26
- resolve();
27
- });
28
- } else {
29
- reject(new Error(`HTTP ${response.statusCode}: ${url}`));
30
- }
31
- })
32
- .on('error', (err) => {
33
- fs.unlink(filepath, () => {}); // Delete partial file
34
- reject(err);
35
- });
36
- });
37
- }
38
-
39
- async function downloadAll() {
40
- console.log('Downloading models to:', outputDir);
41
-
42
- for (const model of models) {
43
- const url = baseUrl + model;
44
- const filepath = path.join(outputDir, model);
45
-
46
- try {
47
- await downloadFile(url, filepath);
48
- } catch (error) {
49
- console.error(`Failed to download ${model}:`, error.message);
50
- }
51
- }
52
-
53
- console.log('Download complete!');
54
- }
55
-
56
- downloadAll();
@@ -1,21 +0,0 @@
1
- const db_module = await import(`${module_path}/db_module/index.mjs`);
2
-
3
- export const get_active_account_profile_info = async function (uid, profile_id) {
4
- try {
5
- const acc_obj = await db_module.get_couch_doc_native('xuda_accounts', uid);
6
-
7
- let active_account_profile_id = profile_id || acc_obj.account_info.active_account_profile_id;
8
-
9
- const account_profile_obj = await db_module.get_app_couch_doc_native(acc_obj.account_project_id, active_account_profile_id);
10
- if (account_profile_obj.share_item_id) {
11
- // set the original profile id if shared
12
- active_account_profile_id = account_profile_obj.share_item_id;
13
- }
14
- const app_id = await get_account_default_project_id(account_profile_obj.shared_from_uid || account_profile_obj.uid);
15
-
16
- return { uid: account_profile_obj.shared_from_uid || uid, account_profile_id: active_account_profile_id, app_id, is_main: active_account_profile_id === acc_obj.account_profile_id, account_profile_obj };
17
- } catch (err) {
18
- console.error('**** get_active_account_profile_info', err);
19
- debugger;
20
- }
21
- };
package/get_user_info.mjs DELETED
@@ -1,126 +0,0 @@
1
- export const get_contact_info = async function (uid, contact_doc, _id) {
2
- let doc = contact_doc;
3
- if (_id) {
4
- doc = await get_contact(uid, _id);
5
- }
6
-
7
- delete doc.metadata;
8
- delete doc.profile_avatar_obj;
9
- delete doc.profile_picture_obj;
10
- delete doc.bio;
11
-
12
- const chat_conversation_count = async function () {
13
- const account_profile_info = await get_active_account_profile_info(uid);
14
-
15
- const counts_ret = await db_module.get_app_couch_view(account_profile_info.app_id, 'chat_conversation_item_counts', {
16
- reduce: true,
17
- group_level: 1,
18
-
19
- startkey: [doc._id, ''],
20
- endkey: [doc._id, 'zzzzzz'],
21
- });
22
-
23
- return counts_ret?.rows?.[0]?.value || 0;
24
- };
25
- const chat_conversation_read = async function () {
26
- const account_profile_info = await get_active_account_profile_info(uid);
27
-
28
- const counts_ret = await db_module.get_app_couch_view(account_profile_info.app_id, 'chat_conversation_item_read', {
29
- reduce: true,
30
- group_level: 2,
31
-
32
- startkey: [uid, doc._id, ''],
33
- endkey: [uid, doc._id, 'zzzzzz'],
34
- });
35
-
36
- return counts_ret?.rows?.[0]?.value || 0;
37
- };
38
-
39
- doc.notifications = 0;
40
- if (doc.contact_uid) {
41
- const account_info_ret = await get_account_name({ uid_query: doc.contact_uid });
42
- if (account_info_ret.code < 0) {
43
- return; // throw new Error(`account ${contact_uid} not found`);
44
- }
45
-
46
- doc.profile_avatar = account_info_ret.data.profile_avatar;
47
-
48
- //membership_plan
49
- // doc.name = `${account_info_ret.data.first_name} ${account_info_ret.data.last_name}`;
50
- doc.name = account_info_ret.data?.account_type === 'business' ? account_info_ret.data.business_name : `${account_info_ret.data.first_name} ${account_info_ret.data.last_name}`;
51
-
52
- if (account_info_ret.data.phone_number) {
53
- try {
54
- doc.phone_number = formatPhoneWithFlag(account_info_ret.data.phone_number, account_info_ret.data.country);
55
- } catch (err) {
56
- // console.error(err);
57
- doc.phone_number = account_info_ret.data.phone_number;
58
- }
59
- }
60
-
61
- if (doc.team_req_id) {
62
- try {
63
- const req_doc = await db_module.get_couch_doc_native('xuda_team', doc.team_req_id);
64
- doc.connection_stat = req_doc.team_req_stat;
65
- doc.connection_type = req_doc.access_type;
66
- doc.connection_uid = req_doc.team_req_from_uid === uid ? req_doc.team_req_to_uid : req_doc.team_req_from_uid;
67
- doc.connection_contact_id = req_doc.team_req_from_contact_id === doc._id ? req_doc.team_req_to_contact_id : req_doc.team_req_from_contact_id;
68
- if (doc.connection_type === 'contact_connection') {
69
- doc.friend_since = req_doc.team_req_date;
70
- }
71
- if (doc.connection_type === 'account_profile') {
72
- try {
73
- const app_id = await get_account_default_project_id(uid);
74
- const account_profile_doc = await db_module.get_app_couch_doc_native(app_id, req_doc.share_item_id);
75
- doc.account_profile = account_profile_doc;
76
- } catch (error) {}
77
- }
78
- } catch (err) {
79
- if (err.message === 'deleted') {
80
- try {
81
- let contact_doc = await get_contact(uid, doc._id);
82
- contact_doc.team_req_id = '';
83
- await save_contact(uid, contact_doc);
84
- } catch (error) {}
85
- }
86
- doc.connection_stat = 0;
87
- }
88
- }
89
-
90
- doc.avatar_source = account_info_ret.data.avatar_source;
91
- // doc.icon_pattern = await get_contact_pattern(doc);
92
- doc.username = account_info_ret.data.username;
93
- doc.business_name = account_info_ret.data.business_name;
94
- doc.address = account_info_ret.data.address;
95
- doc.city = account_info_ret.data.city;
96
- doc.state = account_info_ret.data.state;
97
- doc.zip = account_info_ret.data.zip;
98
- doc.account_type = account_info_ret.data.account_type;
99
- doc.email = doc.email;
100
- if (doc.connection_stat === 3) {
101
- doc.online = account_info_ret.data.online;
102
- }
103
- } else {
104
- doc.email = doc.email;
105
- }
106
-
107
- doc.business_has_person = doc?.business_has_person && doc?.person_info?.person_full_name !== 'Not available';
108
- doc.icon_pattern = await get_contact_pattern(doc);
109
-
110
- delete doc.account_type_info;
111
- delete doc.person_info;
112
- delete doc.business_info;
113
-
114
- const contact_chat_conversation_count_ret = await chat_conversation_count();
115
- const contact_chat_conversation_read_ret = await chat_conversation_read();
116
-
117
- doc.interactions = contact_chat_conversation_count_ret;
118
- doc.notifications = contact_chat_conversation_count_ret - contact_chat_conversation_read_ret;
119
- doc.chats = doc.interactions;
120
-
121
- doc.card_background = get_contact_background(doc);
122
-
123
- doc.border = get_contact_border(doc);
124
-
125
- return doc;
126
- };