This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

vit / explore / src / api.js
4.4 kB 155 lines
1// SPDX-License-Identifier: MIT 2// Copyright (c) 2026 sol pbc 3 4const CORS_HEADERS = { 5 'Access-Control-Allow-Origin': '*', 6 'Access-Control-Allow-Methods': 'GET, OPTIONS', 7 'Access-Control-Allow-Headers': 'Content-Type', 8}; 9 10function json(data, status = 200) { 11 return new Response(JSON.stringify(data), { 12 status, 13 headers: { 'Content-Type': 'application/json', ...CORS_HEADERS }, 14 }); 15} 16 17function parseLimit(value) { 18 const parsed = Number.parseInt(value ?? '50', 10); 19 if (!Number.isFinite(parsed) || parsed <= 0) { 20 return 50; 21 } 22 return Math.min(parsed, 100); 23} 24 25function parseCursor(value) { 26 if (!value) { 27 return null; 28 } 29 30 const parsed = Number.parseInt(value, 10); 31 return Number.isFinite(parsed) && parsed > 0 ? parsed : null; 32} 33 34export async function handleRequest(request, env) { 35 if (request.method === 'OPTIONS') { 36 return new Response(null, { status: 204, headers: CORS_HEADERS }); 37 } 38 39 const url = new URL(request.url); 40 const { pathname, searchParams } = url; 41 42 if (request.method !== 'GET') { 43 return json({ error: 'method not allowed' }, 405); 44 } 45 46 if (pathname === '/api/caps') { 47 const cursor = parseCursor(searchParams.get('cursor')); 48 const limit = parseLimit(searchParams.get('limit')); 49 const beacon = searchParams.get('beacon'); 50 51 const conditions = []; 52 const bindings = []; 53 54 if (beacon) { 55 conditions.push('c.beacon = ?'); 56 bindings.push(beacon); 57 } 58 59 if (cursor) { 60 conditions.push('c.id < ?'); 61 bindings.push(cursor); 62 } 63 64 let sql = 'SELECT c.*, h.handle FROM caps c LEFT JOIN handles h ON c.did = h.did'; 65 if (conditions.length > 0) { 66 sql += ` WHERE ${conditions.join(' AND ')}`; 67 } 68 sql += ' ORDER BY c.id DESC LIMIT ?'; 69 bindings.push(limit); 70 71 const { results } = await env.DB.prepare(sql).bind(...bindings).all(); 72 return json({ 73 caps: results, 74 cursor: results.length > 0 ? results[results.length - 1].id : null, 75 }); 76 } 77 78 if (pathname === '/api/vouches') { 79 const capUri = searchParams.get('cap_uri'); 80 if (!capUri) { 81 return json({ error: 'cap_uri is required' }, 400); 82 } 83 84 const { results } = await env.DB.prepare( 85 `SELECT v.*, h.handle 86 FROM vouches v 87 LEFT JOIN handles h ON v.did = h.did 88 WHERE v.cap_uri = ? 89 ORDER BY v.id DESC`, 90 ) 91 .bind(capUri) 92 .all(); 93 94 return json({ vouches: results }); 95 } 96 97 if (pathname === '/api/beacons') { 98 const { results } = await env.DB.prepare('SELECT * FROM beacons ORDER BY last_activity DESC').all(); 99 return json({ beacons: results }); 100 } 101 102 if (pathname === '/api/skills') { 103 const cursor = parseCursor(searchParams.get('cursor')); 104 const limit = parseLimit(searchParams.get('limit')); 105 const tag = searchParams.get('tag'); 106 107 const conditions = []; 108 const bindings = []; 109 110 if (tag) { 111 conditions.push('INSTR(s.tags, ?) > 0'); 112 bindings.push(tag); 113 } 114 115 if (cursor) { 116 conditions.push('s.id < ?'); 117 bindings.push(cursor); 118 } 119 120 let sql = 'SELECT s.*, h.handle FROM skills s LEFT JOIN handles h ON s.did = h.did'; 121 if (conditions.length > 0) { 122 sql += ` WHERE ${conditions.join(' AND ')}`; 123 } 124 sql += ' ORDER BY s.id DESC LIMIT ?'; 125 bindings.push(limit); 126 127 const { results } = await env.DB.prepare(sql).bind(...bindings).all(); 128 return json({ 129 skills: results, 130 cursor: results.length > 0 ? results[results.length - 1].id : null, 131 }); 132 } 133 134 if (pathname === '/api/stats') { 135 const [caps, vouches, beacons, dids, skills, skillPubs] = await env.DB.batch([ 136 env.DB.prepare('SELECT COUNT(*) as count FROM caps'), 137 env.DB.prepare('SELECT COUNT(*) as count FROM vouches'), 138 env.DB.prepare('SELECT COUNT(*) as count FROM beacons'), 139 env.DB.prepare('SELECT COUNT(DISTINCT did) as count FROM caps'), 140 env.DB.prepare('SELECT COUNT(*) as count FROM skills'), 141 env.DB.prepare('SELECT COUNT(DISTINCT did) as count FROM skills'), 142 ]); 143 144 return json({ 145 total_caps: caps.results[0]?.count ?? 0, 146 total_vouches: vouches.results[0]?.count ?? 0, 147 total_beacons: beacons.results[0]?.count ?? 0, 148 active_dids: dids.results[0]?.count ?? 0, 149 total_skills: skills.results[0]?.count ?? 0, 150 skill_publishers: skillPubs.results[0]?.count ?? 0, 151 }); 152 } 153 154 return json({ error: 'not found' }, 404); 155}