[WIP] A (somewhat barebones) atproto app for creating custom sites without hosting!

server: use "backfill" when avaliable

Changed files
+54 -2
server
+6 -2
server/src/index.ts
··· 1 import root from "./root.ts"; 2 import user from "./user.ts"; 3 4 const ROOT_DOMAIN = Deno.env.get("HOSTNAME") || "localhost"; 5 const PORT = Number(Deno.env.get("PORT")) || 80; ··· 19 } 20 return head; 21 } 22 23 Deno.serve({ port: PORT, hostname: ROOT_DOMAIN }, async (req) => { 24 const reqUrl = new URL(req.url); ··· 42 // did:web example: `vielle.dev.did-web.ROOT_DOMAIN 43 // last segment must be did-plc or did-web 44 if (subdomain.at(-1)?.match(/^did-(web|plc)+$/gm)) { 45 - const res = await user(req, { 46 did: `did:${subdomain.at(-1) === "did-plc" ? "plc" : "web"}:${subdomain.slice(0, -1).join(".")}`, 47 }); 48 return new Response(res.body, { ··· 60 // ex: vielle.dev.ROOT_DOMAIN 61 // cannot contain hyphen in top level domain 62 if (!subdomain.at(-1)?.startsWith("did-") && subdomain.length > 1) { 63 - const res = await user(req, { 64 handle: subdomain.join(".") as `${string}.${string}`, 65 }); 66 return new Response(res.body, {
··· 1 import root from "./root.ts"; 2 import user from "./user.ts"; 3 + import backfill from "./backfill.ts"; 4 + import { routes } from "./db/schema.ts"; 5 6 const ROOT_DOMAIN = Deno.env.get("HOSTNAME") || "localhost"; 7 const PORT = Number(Deno.env.get("PORT")) || 80; ··· 21 } 22 return head; 23 } 24 + 25 + const db = await backfill(); 26 27 Deno.serve({ port: PORT, hostname: ROOT_DOMAIN }, async (req) => { 28 const reqUrl = new URL(req.url); ··· 46 // did:web example: `vielle.dev.did-web.ROOT_DOMAIN 47 // last segment must be did-plc or did-web 48 if (subdomain.at(-1)?.match(/^did-(web|plc)+$/gm)) { 49 + const res = await user(db, req, { 50 did: `did:${subdomain.at(-1) === "did-plc" ? "plc" : "web"}:${subdomain.slice(0, -1).join(".")}`, 51 }); 52 return new Response(res.body, { ··· 64 // ex: vielle.dev.ROOT_DOMAIN 65 // cannot contain hyphen in top level domain 66 if (!subdomain.at(-1)?.startsWith("did-") && subdomain.length > 1) { 67 + const res = await user(db, req, { 68 handle: subdomain.join(".") as `${string}.${string}`, 69 }); 70 return new Response(res.body, {
+7
server/src/types.ts
···
··· 1 + import { LibSQLDatabase } from "drizzle-orm/libsql"; 2 + import { Client } from "@libsql/client"; 3 + import * as schema from "./db/schema.ts"; 4 + 5 + export type db = LibSQLDatabase<typeof schema> & { 6 + $client: Client; 7 + };
+41
server/src/user.ts
··· 10 WebDidDocumentResolver, 11 } from "@atcute/identity-resolver"; 12 import { DevAtcitiesRoute } from "./lexicons/index.ts"; 13 14 const handleResolver = new CompositeHandleResolver({ 15 strategy: "race", ··· 240 } 241 242 export default async function ( 243 req: Request, 244 user: 245 | { handle: `${string}.${string}` } ··· 258 }); 259 } 260 } else did = user.did; 261 262 // resolve did doc 263 let doc;
··· 10 WebDidDocumentResolver, 11 } from "@atcute/identity-resolver"; 12 import { DevAtcitiesRoute } from "./lexicons/index.ts"; 13 + import { db } from "./types.ts"; 14 + import { and, eq } from "drizzle-orm"; 15 + import { routes } from "./db/schema.ts"; 16 17 const handleResolver = new CompositeHandleResolver({ 18 strategy: "race", ··· 243 } 244 245 export default async function ( 246 + db: db, 247 req: Request, 248 user: 249 | { handle: `${string}.${string}` } ··· 262 }); 263 } 264 } else did = user.did; 265 + 266 + // look up in db 267 + const db_res = 268 + ( 269 + await db 270 + .select() 271 + .from(routes) 272 + .where( 273 + and( 274 + eq(routes.did, did), 275 + eq(routes.url_route, new URL(req.url).pathname) 276 + ) 277 + ) 278 + ).at(0) ?? 279 + ( 280 + await db 281 + .select() 282 + .from(routes) 283 + .where(and(eq(routes.did, did), eq(routes.url_route, "404"))) 284 + ).at(0); 285 + 286 + if (db_res) { 287 + try { 288 + const file = await Deno.readFile( 289 + `./blobs/${db_res.did}/${db_res.blob_cid}` 290 + ); 291 + return new Response(file, { 292 + headers: { 293 + "Content-Type": db_res.mime, 294 + }, 295 + }); 296 + } catch { 297 + return new Response( 298 + "Could not find in CDN; TODO: fallback to fetch from pds && add to cdn if missing\nNB: should this be default? index routes in db but only cache used pages" 299 + ); 300 + } 301 + } 302 303 // resolve did doc 304 let doc;