| 123456789101112131415161718192021222324252627282930 |
- // Operator-only maintenance primitive. Callers must authenticate and supply an
- // explicit reviewed identity list; no account is selected by display name alone.
- export async function retireImportedAccounts(db, company, targets, actor, receipt) {
- if (!targets.length || new Set(targets.map(x=>x.objectId)).size !== targets.length) throw Error('invalid_target_list');
- return db.tx(async t=>{
- await t.any('SELECT pg_advisory_xact_lock(hashtext($1))',['direct-sync:'+company]);
- if(await t.oneOrNone('SELECT "runId" FROM "LegacyDirectRun" WHERE company=$1 AND status=\'running\' AND "leaseUntil">now() LIMIT 1',[company])) throw Error('another_sync_running');
- const rows=await t.any('SELECT "objectId","isAdmin",roles,"adminRoleKey","legacyUserId","sourceKey","updatedAt","isDeleted","legacyUserData" FROM "_User" WHERE company=$1 AND "objectId"=ANY($2::text[]) FOR UPDATE',[company,targets.map(x=>x.objectId)]);
- if(rows.length!==targets.length) throw Error('target_missing_or_wrong_company');
- const pending=[];
- for(const expected of targets){
- const row=rows.find(x=>x.objectId===expected.objectId);
- if(row.isAdmin || row.adminRoleKey || (row.roles||[]).some(r=>/admin|manager/.test(r))) throw Error('privileged_target');
- if(row.isDeleted && row.legacyUserData?.RetirementReceipt===receipt) continue;
- if(row.isDeleted || Number(row.legacyUserId)!==Number(expected.legacyUserId) || (row.sourceKey||'')!==(expected.sourceKey||'') || new Date(row.updatedAt).getTime()!==new Date(expected.updatedAt).getTime()) throw Error('target_changed');
- pending.push(row);
- }
- let detached=0;
- for(const row of pending){
- const synthetic=String(row.sourceKey||'').startsWith('reading-unified:user:');
- const legacy={...row.legacyUserData,State:0,SyncExcluded:true,OpsDeletedReason:'user-requested-test-or-foreign-account',RetiredLegacyUserID:Number(row.legacyUserId),RetirementReceipt:receipt,OpsDeletedAt:new Date().toISOString()};
- if(synthetic){delete legacy.UserID;detached++;}
- await t.none('UPDATE "_User" SET "isDeleted"=true,"isDisabled"=true,"legacyUserId"=$3,"legacyUserData"=$4::jsonb,"updatedAt"=now() WHERE company=$1 AND "objectId"=$2',[company,row.objectId,synthetic?null:row.legacyUserId,JSON.stringify(legacy)]);
- }
- const sessions=await t.any('DELETE FROM "_Session" WHERE "user"=ANY($1::text[]) RETURNING "objectId"',[targets.flatMap(x=>[x.objectId,'_User$'+x.objectId])]);
- const result={requested:targets.length,retired:pending.length,alreadyRetired:targets.length-pending.length,detachedSyntheticIds:detached,revokedSessions:sessions.length};
- await t.none('INSERT INTO "SysLog" ("objectId",company,"sourceKey",type1,type2,type3,detail,"createdAt","updatedAt") SELECT substr(md5($1),1,20),$2,$1,\'migration\',\'retire-imported-accounts\',\'user-request\',$3,now(),now() WHERE NOT EXISTS(SELECT 1 FROM "SysLog" WHERE company=$2 AND "sourceKey"=$1)',[receipt,company,JSON.stringify({actor,...result,objectIds:targets.map(x=>x.objectId)})]);
- return result;
- });
- }
|