import {projects} from './projects.js'; const endpoints={ time:[['get','/api/time','Current server Unix milliseconds and ISO UTC time (public)']], workout:[['get','/api/workout','Circuit timing in seconds and exercise names, cues, and easier variants (public)']], libraries:[['get','/api/sources','Source catalogue: names and download URLs (public)']], proxy:[['get','/','Download and cache an HTTP/HTTPS file URL (public); existing URL validation and download policy apply']], 'dorianmarie.com':[['post','/api/contact','URL-encoded name, email, message, g-recaptcha-response. Same-origin; Google reCAPTCHA required. Returns HTML confirmation or error.']], account:[['get','/api/me','Current browser account'],['get','/api/passkeys','List registered passkeys'],['get','/api/preferences','Read account preferences'],['post','/api/preferences','Update preferences: language, theme, timeFormat (24h/12h), weightUnit (kg/lb), temperatureUnit (celsius/fahrenheit)'],['get','/api/keys','List API key names and IDs'],['post','/api/keys','Create key with {name}; token shown once'],['post','/api/keys/revoke','Revoke owned key with {id}']], habits:[['get','/api/data','Read habits and completions; day query YYYY-MM-DD'],['post','/api/habits','Create habit with {name}'],['post','/api/habits/reorder','Reorder habits with {ids:[all active habit IDs in order]}'],['delete','/api/habits/{id}','Remove owned habit; preserve history'],['post','/api/completions','Set completion with {habit_id,day:YYYY-MM-DD,done:boolean}']], heartbeats:[['get','/api/overview','Read monitors and incidents'],['post','/api/monitors','Create monitor with name, interval_seconds, grace_seconds'],['patch','/api/monitors/{id}','Update owned monitor'],['delete','/api/monitors/{id}','Delete owned monitor']], weights:[['post','/api/sync','Merge {records:[{key,value,stamp}]} and retrieve records. Date keys use YYYY-MM-DD, weights and goal in kg; stamp is milliseconds:UUID; null deletes.']], }; export function apiRoute(request,project){ const path=new URL(request.url).pathname; if(!['/api','/api/docs','/api/openapi.json'].includes(path))return null; if(!['GET','HEAD'].includes(request.method))return new Response('method not allowed',{status:405}); const paths={'/api':{get:{summary:'Public application metadata',responses:{200:{description:'Project and API links'}}}},'/api/identity':{get:{summary:'Authenticated identity',security:[{bearer:[]},{cookie:[]}],responses:{200:{description:'User ID and email'},401:{description:'Sign in required'}}}}}; for(const [method,url,summary] of endpoints[project.name]||[])paths[url]={...paths[url],[method]:{summary,security:project.name==='account'?[{cookie:[]}]:['time','workout','libraries','proxy','dorianmarie.com'].includes(project.name)?[]:[{cookie:[]},{bearer:[]}],...(url.includes('{id}')?{parameters:[{name:'id',in:'path',required:true,schema:{type:'string'}}]}:{}),responses:{200:{description:'Success'},401:{description:'Authentication required'}}}}; const schema={openapi:'3.1.0',info:{title:project.name+' API',version:'1.0.0'},servers:[{url:'https://'+(project.hostname||project.name+'.dorianmarie.com')}],paths,components:{securitySchemes:{bearer:{type:'http',scheme:'bearer'},cookie:{type:'apiKey',in:'cookie',name:project.name==='account'?'__Host-account':'__Host-dm-session'}}}}; const headers={'Cache-Control':'no-store','Content-Type':path==='/api/docs'?'text/html; charset=utf-8':'application/json'}; const body=path==='/api/docs'?`${project.name} API

${project.name} API

OpenAPI specification

Create API keys at account. Send Authorization: Bearer YOUR_KEY. Browser requests use secure session cookies. Mutations require an Origin header matching the application origin; command-line clients must include it too. Data belongs to the authenticated account. Invalid credentials return 401; account outages return 503.

curl -H 'Authorization: Bearer YOUR_KEY' https://${project.hostname||project.name+'.dorianmarie.com'}/api/identity
${Object.entries(paths).flatMap(([url,methods])=>Object.entries(methods).map(([method,info])=>``)).join('')}
MethodPathPurpose
${method.toUpperCase()}${url}${info.summary}
${project.name==='account'?'

Passkeys, settings, and key management require an account browser session; API keys cannot create further keys or change credentials.

':''}${project.name==='heartbeats'?'

Monitor and ping request bodies. Existing heartbeat tokens remain accepted for compatibility.

':''}

open application

`:JSON.stringify(path==='/api/openapi.json'?schema:{project:project.name,description:project.description,documentation:'/api/docs',openapi:'/api/openapi.json',...(project.name==='dorianmarie.com'?{applications:projects.map(p=>({name:p.name,url:'https://'+(p.hostname||p.name+'.dorianmarie.com')}))}:{})}); return new Response(request.method==='HEAD'?null:body,{headers}); }