@xuda.io/account_module 1.2.2278 → 1.2.2280

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
@@ -367,7 +367,13 @@ export const increment_account_usage = async function (req) {
367
367
 
368
368
  if (Date.now() - doc.ts >= interval) {
369
369
  const plan = _conf.PLAN_OBJ[account_data.membership_plan];
370
- if (!plan) throw new Error(`${account_data.account_info.first_name + ' ' + account_data.account_info.last_name} has no plan in file`);
370
+ if (!plan) {
371
+ // Only report missing plans for stat 3 (verified) accounts. Stat 1/2
372
+ // (unverified stubs, temp-pass invites, widget signups) have no plan
373
+ // until their first real login by design — skip silently.
374
+ if (account_data.stat === 3) throw new Error(`${account_data.account_info.first_name + ' ' + account_data.account_info.last_name} has no plan in file`);
375
+ return;
376
+ }
371
377
  const drive_size_total = account_data.user_drive_size + account_data.studio_drive_size + account_data.workspace_drive_size + account_data.builds_drive_size + account_data.plugins_drive_size;
372
378
  if (bytesToGB(drive_size_total) > plan.features.drive) {
373
379
  var price_per_hour = (bytesToGB(drive_size_total) * _conf.PRICE_OBJ.price_per_gb_drive) / _conf.PRICE_OBJ.avg_hours_in_month;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/account_module",
3
- "version": "1.2.2278",
3
+ "version": "1.2.2280",
4
4
  "description": "Xuda Account Server Module",
5
5
  "main": "index.mjs",
6
6
  "dependencies": {
@@ -0,0 +1,90 @@
1
+ // One-off cleanup: soft-delete orphan account-project shells.
2
+ //
3
+ // A half-provisioned account can accumulate multiple `is_account_project`
4
+ // projects — login_maintenance creates a fresh one on every retry but only ever
5
+ // adopts ONE as the account doc's `account_project_id`. The non-adopted shells
6
+ // are empty orphans (the "4 Jonathan's Project" pileup). This soft-deletes the
7
+ // orphans (app_status_code = 4) so they drop out of the sidebar, keeping the
8
+ // adopted/live one untouched.
9
+ //
10
+ // The create_project resilience fix stops the pileup from happening again; this
11
+ // just cleans up accounts that already accumulated shells.
12
+ //
13
+ // Usage (from XUDA_HOME, same env the cpi workers run with — XUDA_HOME +
14
+ // XUDA_CONFIG set):
15
+ // node cpi/account_module/scripts/cleanup_orphan_account_projects.mjs <uid> # DRY RUN (lists, writes nothing)
16
+ // node cpi/account_module/scripts/cleanup_orphan_account_projects.mjs <uid> --apply # actually soft-delete
17
+ //
18
+ // Idempotent — re-running skips already-deleted docs. Hard-aborts if the
19
+ // account has no account_project_id (so it can never delete EVERY project).
20
+
21
+ import path from 'path';
22
+
23
+ if (!global._conf) {
24
+ global._conf = (await import(path.join(process.env.XUDA_HOME, "common", "load_conf.mjs"))).loadConf();
25
+ }
26
+
27
+ const uid = process.argv[2];
28
+ const apply = process.argv.includes('--apply');
29
+ if (!uid || uid.startsWith('--')) {
30
+ console.error('usage: node cleanup_orphan_account_projects.mjs <uid> [--apply]');
31
+ process.exit(1);
32
+ }
33
+
34
+ const module_path = path.join(process.env.XUDA_HOME, 'cpi') + (!_conf.is_debug ? '/node_modules/@xuda.io' : '');
35
+ const db_module = await import(`${module_path}/db_module/index.mjs`);
36
+
37
+ // 1. The account doc tells us which project is the live one to KEEP.
38
+ const acct = await db_module.get_couch_doc('xuda_accounts', uid);
39
+ if (acct.code < 0) {
40
+ console.error('account not found:', uid, acct.data);
41
+ process.exit(1);
42
+ }
43
+ const keep = acct.data.account_project_id;
44
+ console.log('account', uid, '→ keep account_project_id:', keep || '(NONE SET)');
45
+
46
+ // 2. Every is_account_project project owned by this account (full scan — fine on
47
+ // the small set an account owns; ignore_warning silences the no-index notice).
48
+ const found = await db_module.find_couch_query(
49
+ 'xuda_master',
50
+ { selector: { app_uId: uid, is_account_project: true }, limit: 9999 },
51
+ true,
52
+ );
53
+ const docs = found?.data?.docs || found?.docs || [];
54
+ console.log(
55
+ 'found',
56
+ docs.length,
57
+ 'is_account_project doc(s):',
58
+ docs.map((d) => `${d._id}${d._id === keep ? ' (KEEP)' : ''}${d.app_status_code === 4 ? ' (already deleted)' : ''}`),
59
+ );
60
+
61
+ // Safety: never run blind on an account that hasn't adopted a project yet —
62
+ // otherwise every project would look like an orphan and we'd wipe them all.
63
+ if (!keep) {
64
+ console.error('\nABORT: account has no account_project_id set. Re-provision it first (re-login so login_maintenance adopts one), then re-run.');
65
+ process.exit(1);
66
+ }
67
+
68
+ // 3. Orphans = not the adopted one + not already soft-deleted.
69
+ const orphans = docs.filter((d) => d._id !== keep && d.app_status_code !== 4);
70
+ console.log('\norphans to soft-delete (' + orphans.length + '):', orphans.map((d) => d._id));
71
+
72
+ if (!orphans.length) {
73
+ console.log('\nnothing to clean. done.');
74
+ process.exit(0);
75
+ }
76
+
77
+ if (!apply) {
78
+ console.log('\nDRY RUN — re-run with --apply to soft-delete the above. Nothing was written.');
79
+ process.exit(0);
80
+ }
81
+
82
+ for (const d of orphans) {
83
+ d.app_status_code = 4;
84
+ d.stat_reason = 'orphan account-project cleanup';
85
+ d.date_updated_ts = Date.now();
86
+ const r = await db_module.save_couch_doc('xuda_master', d);
87
+ console.log('soft-deleted', d._id, '→ code', r.code);
88
+ }
89
+ console.log('\ndone — soft-deleted', orphans.length, 'orphan(s). The live project', keep, 'was kept.');
90
+ process.exit(0);