[WIP] A (somewhat barebones) atproto app for creating custom sites without hosting!
at main 116 lines 2.9 kB view raw
1/// <reference types="@atcute/atproto" /> 2import { 3 CompositeHandleResolver, 4 DohJsonHandleResolver, 5 WellKnownHandleResolver, 6} from "@atcute/identity-resolver"; 7import { and, eq } from "drizzle-orm"; 8import { routes } from "../db/schema.ts"; 9import { type db, isDid, ROOT_DOMAIN } from "../utils.ts"; 10import ascii from "./ascii.txt" with { type: "text" }; 11import storeBlob from "../backfill/blob.ts"; 12 13const handleResolver = new CompositeHandleResolver({ 14 strategy: "race", 15 methods: { 16 dns: new DohJsonHandleResolver({ 17 dohUrl: "https://mozilla.cloudflare-dns.com/dns-query", 18 }), 19 http: new WellKnownHandleResolver(), 20 }, 21}); 22 23export default async function ( 24 db: db, 25 req: Request, 26 user: 27 | { handle: `${string}.${string}` } 28 | { did: `did:plc:${string}` | `did:web:${string}` } 29): Promise<Response> { 30 // if handle: resolve did 31 let did: `did:${"plc" | "web"}:${string}`; 32 if ("handle" in user) { 33 try { 34 // cast bc i know it will be `string.string` 35 did = await handleResolver.resolve(user.handle); 36 } catch { 37 return new Response( 38 `${ascii} 39 40This handle could not be resolved. 41`, 42 { 43 status: 500, 44 statusText: "Internal Server Error", 45 } 46 ); 47 } 48 } else did = user.did; 49 50 // look up in db 51 const db_res = 52 ( 53 await db 54 .select() 55 .from(routes) 56 .where( 57 and( 58 eq(routes.did, did), 59 eq(routes.url_route, new URL(req.url).pathname) 60 ) 61 ) 62 ).at(0) ?? 63 ( 64 await db 65 .select() 66 .from(routes) 67 .where(and(eq(routes.did, did), eq(routes.url_route, "404"))) 68 ).at(0); 69 70 if (!db_res) { 71 return new Response(`${ascii} 72 73404: The user has no atcities site or is missing a 404 page. 74 75If you're the owner of this account, head to https://atcities.dev/ for more information. 76The index of this account is at https://${ 77 "handle" in user 78 ? user.handle 79 : user.did.split(":").at(-1) + ".did-" + user.did.split(":").at(1) 80 }.${ROOT_DOMAIN}/ 81`); 82 } 83 try { 84 const file = await Deno.readFile( 85 `./blobs/${db_res.did}/${db_res.blob_cid}` 86 ); 87 return new Response(file, { 88 headers: { 89 "Content-Type": db_res.mime, 90 }, 91 }); 92 } catch { 93 if (!isDid(db_res.did)) 94 return new Response(`${ascii} 95 96${db_res.did} is not a valid DID. This account could not be resolved 97`); 98 try { 99 const blob = await storeBlob(db_res.did, db_res.blob_cid); 100 return new Response(blob, { 101 headers: { 102 "Content-Type": db_res.mime, 103 }, 104 }); 105 } catch (e) { 106 console.error(e); 107 return new Response(`${ascii} 108 109This page could not be resolved. Either: 110- The user has no PDS 111- The blob does not exist 112- The user has been manually hidden for uploading illegal content. 113`); 114 } 115 } 116}