Files
4ev.link/functions/api/links/update.js

2 lines
2.0 KiB
JavaScript

const R=new Set(["api","dash","admin","login","logout","signin","signup","register","account","settings","profile","password","user","users","link","links","url","urls","robots","sitemap","favicon","well-known","assets","static","img","js","css","public"]);export async function onRequestPost({request:e,env:s}){try{const{"cf-turnstile-response":t,...a}=await e.json(),n=e.headers.get("CF-Connecting-IP"),r=new FormData;r.append("secret",s.TURNSTILE_KEY),r.append("response",t),n&&r.append("remoteip",n);const i=await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify",{method:"POST",body:r});if(!(await i.json()).success)return new Response("CAPTCHA verification failed.",{status:403});const{username:o,pass_hash:l,changes:c}=a;if(!o||!l||!Array.isArray(c))return new Response("Missing fields",{status:400});const d=await s.D1_EV.prepare("SELECT pass_hash,custom_slugs FROM users WHERE username = ?").bind(o).first();if(d?.pass_hash!==l)return new Response("Invalid credentials",{status:401});const u=new Set(JSON.parse(d.custom_slugs||"[]"));for(const g of c){const{originalSlug:p,slug:h,destination_url:f}=g;if(!u.has(p))return new Response(`Permission denied: ${p}`,{status:403});if(!h||!f)return new Response("Missing slug or destination",{status:400});if(h!==p){if(R.has(h.toLowerCase())||!/^[a-zA-Z0-9-]{3,32}$/.test(h))return new Response(`Invalid slug: ${h}`,{status:400});if(await s.KV_EV.get(h))return new Response(`Slug taken: ${h}`,{status:409})}}const m=[],p=new Set(u);for(const g of c){const{originalSlug:h,slug:f,destination_url:w}=g;let b=w.startsWith("http")?w:`https://${w}`;try{new URL(b)}catch{return new Response(`Invalid URL: ${w}`,{status:400})}const y=b.replace(/^https?:\/\//,"");f!==h&&(m.push(s.KV_EV.delete(h)),p.delete(h)),m.push(s.KV_EV.put(f,y)),p.add(f)}return await Promise.all([...m,s.D1_EV.prepare("UPDATE users SET custom_slugs = ? WHERE username = ?").bind(JSON.stringify(Array.from(p)),o).run()]),new Response("Links updated",{status:200})}catch(t){return new Response(t.message,{status:500})}}