@xuda.io/account_module 1.2.2299 → 1.2.2301

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/index.mjs +89 -24
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -55,6 +55,18 @@ const module_path = path.join(process.env.XUDA_HOME, 'cpi') + (!_conf.is_debug ?
55
55
 
56
56
  const db_module = await import(`${module_path}/db_module/index.mjs`);
57
57
 
58
+ // Sort keys for the dashboard list tabs, per docType. Each entry is a fallback
59
+ // chain (first defined value wins) because doc shapes differ. A profile keeps
60
+ // its display name under profile_name, a contact under name. See
61
+ // db_module.find_app_couch_sorted_page.
62
+ const LIST_SORT_PATHS = {
63
+ contact: { created: ['date_created_ts', 'date_created', 'ts'], updated: ['ts', 'date_created_ts'], name: ['name', 'contact_name', 'email'] },
64
+ account_profile: { created: ['date_created_ts', 'date_created', 'ts'], updated: ['ts', 'date_created_ts'], name: ['profile_name', 'name'] },
65
+ };
66
+
67
+ // Dashboard list tabs whose sort preference is remembered per profile.
68
+ const LIST_SORT_TABS = ['ai_chats', 'apps', 'contacts', 'ai_agents', 'account_profiles'];
69
+
58
70
  const ai_ms = await import(`${module_path}/ai_module/index_ms.mjs`);
59
71
  const drive_ms = await import(`${module_path}/drive_module/index_ms.mjs`);
60
72
 
@@ -307,7 +319,6 @@ export const update_account_info = async function (req, job_id, headers) {
307
319
 
308
320
  if (account_obj.account_info?.profile_picture) {
309
321
  if (!account_obj.account_info?.profile_avatar && account_obj.account_info.profile_avatar_stat !== 2) {
310
- debugger;
311
322
  set_account_profile_picture(uid, uid, account_obj.account_info, job_id, headers, account_profile_info);
312
323
  }
313
324
  }
@@ -371,7 +382,7 @@ export const update_account_info = async function (req, job_id, headers) {
371
382
  return save_ret;
372
383
  };
373
384
  export const update_account_preferences = async function (req) {
374
- const { uid, hide_create_button, default_dashboard, sidebar_style, enable_thumbnail_avatar_generation, sidenav_mode, newsletter_opt_in } = req;
385
+ const { uid, hide_create_button, default_dashboard, sidebar_style, enable_thumbnail_avatar_generation, sidenav_mode, newsletter_opt_in, dashboard_sort } = req;
375
386
  try {
376
387
  let account_doc = await db_module.get_couch_doc_native('xuda_accounts', uid);
377
388
  account_doc.ts = Date.now();
@@ -387,6 +398,32 @@ export const update_account_preferences = async function (req) {
387
398
  // flag via router /newsletter/unsubscribe. Only touched when the caller sends it.
388
399
  if (newsletter_opt_in !== undefined) account_doc.preferences.newsletter_opt_in = !!newsletter_opt_in;
389
400
 
401
+ // Dashboard list sort, remembered per PROFILE and per tab so switching
402
+ // profiles brings back that profile's own order:
403
+ // preferences.dashboard_sort[<account_profile_id>][<tab>] = { by, dir }
404
+ // Callers send one tab at a time ({ tab, sort_by, sort_dir, profile_id })
405
+ // and the merge below is surgical, so saving the Contacts order can never
406
+ // drop the one saved for AI Chats or for another profile. Sending
407
+ // sort_by:null clears that tab back to the default order.
408
+ if (dashboard_sort !== undefined) {
409
+ const { tab, sort_by, sort_dir } = dashboard_sort || {};
410
+ if (LIST_SORT_TABS.includes(tab)) {
411
+ const profile_info = await get_active_account_profile_info(uid, dashboard_sort?.profile_id);
412
+ const profile_key = profile_info?.account_profile_id || 'default';
413
+ const all = { ...(account_doc.preferences.dashboard_sort || {}) };
414
+ const for_profile = { ...(all[profile_key] || {}) };
415
+
416
+ if (['date', 'updated', 'name'].includes(sort_by)) {
417
+ for_profile[tab] = { by: sort_by, dir: sort_dir === 'asc' ? 'asc' : 'desc' };
418
+ } else {
419
+ delete for_profile[tab];
420
+ }
421
+
422
+ all[profile_key] = for_profile;
423
+ account_doc.preferences.dashboard_sort = all;
424
+ }
425
+ }
426
+
390
427
  const save_ret = await db_module.save_couch_doc('xuda_accounts', account_doc);
391
428
  if (save_ret.code < 0) {
392
429
  throw new Error(save_ret.data);
@@ -3222,7 +3259,6 @@ export const get_hosting_plan = function (req) {
3222
3259
  if (_conf.PRICE_OBJ.server_slugs[app_obj.app_hosting.app_server_type]) {
3223
3260
  return _conf.PRICE_OBJ.server_slugs[app_obj.app_hosting.app_server_type];
3224
3261
  }
3225
- debugger;
3226
3262
  console.error('error: ' + app_obj.app_hosting.app_server_type + ' not found in PRICE_OBJ.server_slugs');
3227
3263
  return { cpu: 0, price: 0 };
3228
3264
  }
@@ -5468,17 +5504,25 @@ export const get_contacts = async function (req, job_id, headers) {
5468
5504
  let contacts = { docs: [], total_docs: 0 };
5469
5505
 
5470
5506
  if (filter_type !== 'pending') {
5471
- contacts = await find_contact_query(account_profile_info.uid, opt);
5472
- if (!limit || contact_id) {
5473
- contacts.total_docs = contacts.docs.length;
5507
+ // User-picked order from the dashboard list controls, see
5508
+ // db_module.find_app_couch_sorted_page. Null when none was asked for,
5509
+ // which leaves the default newest-first path below untouched.
5510
+ const sorted = contact_id ? null : await db_module.find_app_couch_sorted_page(account_profile_info.app_id, opt, req, LIST_SORT_PATHS.contact);
5511
+ if (sorted) {
5512
+ contacts = sorted;
5474
5513
  } else {
5475
- delete opt.sort;
5476
- delete opt.skip;
5477
- opt.limit = 9999;
5478
- opt.fields = ['_id'];
5514
+ contacts = await find_contact_query(account_profile_info.uid, opt);
5515
+ if (!limit || contact_id) {
5516
+ contacts.total_docs = contacts.docs.length;
5517
+ } else {
5518
+ delete opt.sort;
5519
+ delete opt.skip;
5520
+ opt.limit = 9999;
5521
+ opt.fields = ['_id'];
5479
5522
 
5480
- const counter = await find_contact_query(account_profile_info.uid, opt);
5481
- contacts.total_docs = counter.docs.length;
5523
+ const counter = await find_contact_query(account_profile_info.uid, opt);
5524
+ contacts.total_docs = counter.docs.length;
5525
+ }
5482
5526
  }
5483
5527
  }
5484
5528
 
@@ -5943,13 +5987,26 @@ export const get_account_profile_info = async function (uid, contact_profile_doc
5943
5987
  if (account_info_ret.code < 0) {
5944
5988
  return;
5945
5989
  }
5946
- if (!doc.profile_avatar) {
5990
+ // The main profile IS the account's own face: always mirror the account
5991
+ // avatar/picture on it, so the main-profile card matches the account avatar.
5992
+ // A divergent value stored on the main profile doc (e.g. a stale AI-generated
5993
+ // avatar) must not win. Non-main profiles keep their own, falling back to the
5994
+ // account avatar only when they have none.
5995
+ if (doc.main || !doc.profile_avatar) {
5947
5996
  doc.profile_avatar = account_info_ret.data.profile_avatar;
5948
5997
  }
5949
- if (!doc.profile_picture) {
5998
+ if (doc.main || !doc.profile_picture) {
5950
5999
  doc.profile_picture = account_info_ret.data.profile_picture;
5951
6000
  }
5952
6001
 
6002
+ // The main profile is the account's OWN identity: it must never auto-respond
6003
+ // (you don't auto-reply to yourself). Force it off in the read model so the
6004
+ // card hides the control and no read-based consumer treats it as on. The
6005
+ // runtime (ai_module.auto_response) also hard-skips the main profile.
6006
+ if (doc.main) {
6007
+ doc.auto_respond = false;
6008
+ }
6009
+
5953
6010
  if (!doc.account_type) {
5954
6011
  doc.account_type = account_info_ret.data.account_type;
5955
6012
  }
@@ -6146,17 +6203,26 @@ export const get_account_profiles = async function (req) {
6146
6203
  let profiles = { docs: [], total_docs: 0 };
6147
6204
 
6148
6205
  if (filter_type !== 'pending') {
6149
- profiles = await db_module.find_app_couch_query(app_id, opt);
6150
- if (!limit || profile_id) {
6151
- profiles.total_docs = profiles.docs.length;
6206
+ // User-picked order from the dashboard list controls, see
6207
+ // db_module.find_app_couch_sorted_page. Null when none was asked for,
6208
+ // which leaves the default newest-first path below untouched. The main
6209
+ // profile is still floated to the top of the page after enrichment.
6210
+ const sorted = profile_id ? null : await db_module.find_app_couch_sorted_page(app_id, opt, req, LIST_SORT_PATHS.account_profile);
6211
+ if (sorted) {
6212
+ profiles = sorted;
6152
6213
  } else {
6153
- delete opt.sort;
6154
- delete opt.skip;
6155
- opt.limit = 9999;
6156
- opt.fields = ['_id'];
6214
+ profiles = await db_module.find_app_couch_query(app_id, opt);
6215
+ if (!limit || profile_id) {
6216
+ profiles.total_docs = profiles.docs.length;
6217
+ } else {
6218
+ delete opt.sort;
6219
+ delete opt.skip;
6220
+ opt.limit = 9999;
6221
+ opt.fields = ['_id'];
6157
6222
 
6158
- const counter = await db_module.find_app_couch_query(app_id, opt);
6159
- profiles.total_docs = counter.docs.length;
6223
+ const counter = await db_module.find_app_couch_query(app_id, opt);
6224
+ profiles.total_docs = counter.docs.length;
6225
+ }
6160
6226
  }
6161
6227
  }
6162
6228
 
@@ -7092,7 +7158,6 @@ export const get_account_ai_usage_old = async function (req, job_id, headers) {
7092
7158
  group_level: 999,
7093
7159
  });
7094
7160
  // console.log('ai_usage', ai_usage.rows);
7095
- debugger;
7096
7161
  let total_usage = 0;
7097
7162
  let profile = {};
7098
7163
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/account_module",
3
- "version": "1.2.2299",
3
+ "version": "1.2.2301",
4
4
  "description": "Xuda Account Server Module",
5
5
  "main": "index.mjs",
6
6
  "dependencies": {