Files
Enelix-EMS/services/netplan-v4/integrations/netplan-v4-bridge.mjs
T

40 lines
3.0 KiB
JavaScript

/** Scoped opt-in proxy. Never intercept the existing V1 schedule. */
export function createPlannerV4Bridge({configuredPrognosisPlant,roleAllowed,bodyJson,json,
deviceActivation,checkDeviceRate,ownedLicenseState,serviceToken,
upstreamUrl='http://netplan-v4:9100',fetchImpl=fetch}){
const base=new URL(upstreamUrl);
if(!['http:','https:'].includes(base.protocol)||base.username||base.password)throw new Error('Invalid V4 URL');
async function forward(req,res,installation,suffix,method){
if(!serviceToken){json(res,503,{error:'V4 ist nicht angebunden.'});return;}
const url=new URL(`/internal/v2/prognosis/${encodeURIComponent(installation)}/planner${suffix}`,base);
let response;
const payload=method==='GET'?undefined:await bodyJson(req);
try{response=await fetchImpl(url,{method,headers:{'X-Enelix-Service-Token':serviceToken,'Content-Type':'application/json'},body:payload===undefined?undefined:JSON.stringify(payload),signal:AbortSignal.timeout(10000),redirect:'error'});}
catch{json(res,503,{error:'V4 nicht erreichbar. Bestehende Regelung bleibt unveraendert.'});return;}
const data=await response.json().catch(()=>null);
if(!response.ok){const code=[400,401,403,404,409,413].includes(response.status)?response.status:503;json(res,code,{error:code===409?'Versionskonflikt: Ansicht neu laden.':'V4-Anfrage abgelehnt oder Dienst nicht verfuegbar.'});return;}
if(!data||typeof data!=='object'){json(res,503,{error:'Ungueltige V4-Antwort.'});return;}
json(res,200,data);
}
return async function(req,res,url){
const c=url.pathname.match(/^\/api\/plants\/([0-9a-f-]{36})\/prognosis\/planner-v4(?:\/(settings|replan))?$/i);
if(c){
const action=c[2]||'',method={'':'GET',settings:'PUT',replan:'POST'}[action];
if(req.method!==method){json(res,405,{error:'Methode nicht erlaubt.'});return true;}
const access=configuredPrognosisPlant(req,res,c[1],method!=='GET');if(!access)return true;
if(Number(access.license.quantities.grid_schedule||0)<1){json(res,403,{error:'Netzfahrplan nicht lizenziert.'});return true;}
if(method!=='GET'&&!roleAllowed(access.session,['owner','admin','operator'])){json(res,403,{error:'Keine Aenderungsberechtigung.'});return true;}
await forward(req,res,access.plant.installation_id,action?`/${action}`:'',method);return true;
}
const d=url.pathname.match(/^\/api\/v1\/installations\/([0-9a-f-]{36})\/prognosis\/planner-v4(?:\/(operation|ack|measurements))?$/i);
if(d){
const action=d[2]||'';if(req.method!==(action?'POST':'GET')){json(res,405,{error:'Methode nicht erlaubt.'});return true;}
if(!checkDeviceRate(req,d[1],'planner-v4')){json(res,429,{error:'Zu viele Anfragen.'});return true;}
const activation=deviceActivation(req,res,d[1]);if(!activation)return true;
if(Number(ownedLicenseState(activation.plant_id).quantities.grid_schedule||0)<1){json(res,403,{error:'Netzfahrplan nicht lizenziert.'});return true;}
await forward(req,res,d[1],action==='operation'?'/inputs/operation':action==='measurements'?'/measurements':action?'/ack':'',req.method);return true;
}
return false;
};
}