| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657 |
- import fs from 'node:fs';
- import crypto from 'node:crypto';
- import {request,adminHeaders,functionsUrl,redact} from '../server/scripts/platform-client.mjs';
- import {save,load,unwrap,read} from './lib/research-enrichment.mjs';
- const name='ksfResearchCleanup20261111',endpointWork='work/library-cleanup-20261111',work=process.env.KSF_CLEANUP_WORK||endpointWork;
- fs.mkdirSync(work,{recursive:true});
- const requestedMode=process.argv[2];
- const mode=requestedMode==='inventory-after'?'inventory':requestedMode;
- const sha=x=>crypto.createHash('sha256').update(x).digest('hex');
- if(mode==='deploy') {
- const source=fs.readFileSync('server/src/research-cleanup-maintenance.mjs','utf8').replace(/^export /gm,'');
- const helper=fs.readFileSync('server/voc-library/research_cleanup.py','utf8'),tests=fs.readFileSync('server/voc-library/test_research_cleanup.py','utf8');
- const entry=`\nasync function handler(request,response){const crypto=await import('node:crypto');const fs=await import('node:fs/promises');const {spawn}=await import('node:child_process');return createResearchCleanupMaintenance({Parse,crypto,fs,spawn,helperSource:${JSON.stringify(helper)},testSource:${JSON.stringify(tests)},loadDatabase:async()=>{const m=await import('parse-server/lib/Config.js');const C=m.default?.default||m.default;const a=C.get('FKieRbIoGt_main').database?.adapter;if(a?.constructor?.name!=='PostgresStorageAdapter'||typeof a._client?.tx!=='function')throw Error('INCOMPATIBLE_DATABASE');return a._client;}})(request,response);}\n`;
- const code=source+entry;new Function(code);
- const matches=(await request('GET','/classes/Function?'+new URLSearchParams({where:JSON.stringify({$or:[{name},{path:'/'+name}]}),limit:'3'}))).results;
- if(matches.length>1||matches.some(x=>x.name!==name||x.path!=='/'+name))throw Error('FUNCTION_CONFLICT');
- if(matches.length) {
- const prior=load(endpointWork+'/maintenance-deployed.json');
- if(sha(matches[0].code)!==prior.sha256)throw Error('UNKNOWN_FUNCTION_BASELINE');
- save(work+'/maintenance-before-'+Date.now()+'.json',matches[0]);
- }
- let id=matches[0]?.objectId;
- if(!id)id=(await request('POST','/classes/Function',{name,path:'/'+name,code,ACL:{}})).objectId;
- else if(matches[0].code!==code)await request('PUT','/classes/Function/'+id,{code});
- const back=await request('GET','/classes/Function/'+id);
- if(back.code!==code)throw Error('FUNCTION_READBACK_FAILED');
- save(endpointWork+'/maintenance-deployed.json',{ok:true,name,objectId:id,sha256:sha(code),deployedAt:new Date().toISOString()});
- fs.writeFileSync(endpointWork+'/maintenance-deployed.js',code);
- console.log(JSON.stringify({ok:true,name,sha256:sha(code)}));
- } else if(['inventory','runtime','selftest','plan','apply','status'].includes(mode)) {
- const input=process.argv[3]?load(process.argv[3]):{};
- if(mode==='apply'){
- const planned=unwrap(load(work+'/plan-result.json'));
- if(!planned.ok||!planned.retainedRowsVerified||!planned.sourceRowsPreserved||!planned.foreignKeysVerified||planned.planSha256!==input.planSha256)throw Error('SUCCESSFUL_CLEANUP_PLAN_REQUIRED');
- input.expectedResult={generation:planned.generation,fingerprint:planned.fingerprint};
- const current=await read('contract');save(work+'/contract-before-apply.json',current);
- if(current.generation!==input.plan.baseGeneration)throw Error('GENERATION_CHANGED_SINCE_CLEANUP_PLAN');
- }
- const startedAt=new Date().toISOString();
- const res=await fetch(functionsUrl,{method:'POST',headers:adminHeaders,redirect:'error',signal:AbortSignal.timeout(580000),body:JSON.stringify({path:'/'+name,params:{...input,action:mode}})});
- const raw=Buffer.from(await res.arrayBuffer());
- fs.writeFileSync(work+'/'+requestedMode+'-response-'+Date.now()+'.json',raw);
- const response={http:res.status,body:JSON.parse(raw),startedAt,finishedAt:new Date().toISOString()};
- save(work+'/'+requestedMode+'-result.json',response);
- const r=unwrap(response);
- if(!r.ok)throw Error(JSON.stringify(r.error));
- console.log(JSON.stringify(mode==='inventory'?{ok:r.ok,generation:r.generation,products:r.products.length,social:r.social.length}:r));
- } else if(mode==='remove-endpoint') {
- const d=load(endpointWork+'/maintenance-deployed.json');
- const old=await request('GET','/classes/Function/'+d.objectId);
- if(sha(old.code)!==d.sha256)throw Error('UNKNOWN_FUNCTION_BASELINE');
- await request('DELETE','/classes/Function/'+d.objectId);
- const remaining=await request('GET','/classes/Function?'+new URLSearchParams({where:JSON.stringify({name}),limit:'1'}));
- if(remaining.results.length)throw Error('MAINTENANCE_ENDPOINT_STILL_PRESENT');
- save(work+'/maintenance-removed.json',{ok:true,name,objectId:d.objectId,removedAt:new Date().toISOString()});
- console.log(JSON.stringify({ok:true,removed:true,name}));
- } else throw Error('MAINTENANCE_MODE_REQUIRED');
|