[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 1 import root from "./root.ts"; 2 2 import user from "./user.ts"; 3 + import backfill from "./backfill.ts"; 4 + import { routes } from "./db/schema.ts"; 3 5 4 6 const ROOT_DOMAIN = Deno.env.get("HOSTNAME") || "localhost"; 5 7 const PORT = Number(Deno.env.get("PORT")) || 80; ··· 19 21 } 20 22 return head; 21 23 } 24 + 25 + const db = await backfill(); 22 26 23 27 Deno.serve({ port: PORT, hostname: ROOT_DOMAIN }, async (req) => { 24 28 const reqUrl = new URL(req.url); ··· 42 46 // did:web example: `vielle.dev.did-web.ROOT_DOMAIN 43 47 // last segment must be did-plc or did-web 44 48 if (subdomain.at(-1)?.match(/^did-(web|plc)+$/gm)) { 45 - const res = await user(req, { 49 + const res = await user(db, req, { 46 50 did: `did:${subdomain.at(-1) === "did-plc" ? "plc" : "web"}:${subdomain.slice(0, -1).join(".")}`, 47 51 }); 48 52 return new Response(res.body, { ··· 60 64 // ex: vielle.dev.ROOT_DOMAIN 61 65 // cannot contain hyphen in top level domain 62 66 if (!subdomain.at(-1)?.startsWith("did-") && subdomain.length > 1) { 63 - const res = await user(req, { 67 + const res = await user(db, req, { 64 68 handle: subdomain.join(".") as `${string}.${string}`, 65 69 }); 66 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 10 WebDidDocumentResolver, 11 11 } from "@atcute/identity-resolver"; 12 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"; 13 16 14 17 const handleResolver = new CompositeHandleResolver({ 15 18 strategy: "race", ··· 240 243 } 241 244 242 245 export default async function ( 246 + db: db, 243 247 req: Request, 244 248 user: 245 249 | { handle: `${string}.${string}` } ··· 258 262 }); 259 263 } 260 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 + } 261 302 262 303 // resolve did doc 263 304 let doc;