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

server: store blobs of routes on request

note: this isn't production ready, as it still stores CSAM and other illegal content
moderation is the next stage

vielle.dev 2910f4ff 4ca77016

verified
Changed files
+60 -53
server
blobs
did:web:invalid
src
backfill
routes
+2 -1
.gitignore
··· 1 1 server/node_modules/* 2 2 server/local.db 3 - server/.env 3 + server/.env 4 + server/blobs
server/blobs/did:web:invalid/cat

This is a binary file and will not be displayed.

-11
server/blobs/did:web:invalid/hello
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>hi</title> 7 - </head> 8 - <body> 9 - hi 10 - </body> 11 - </html>
-19
server/blobs/did:web:invalid/root
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>did:web:invalid from cdn</title> 7 - </head> 8 - <body> 9 - this site is served from the cdn.<br /> 10 - all pages: 11 - <ul> 12 - <li><a href="404-page-here">404</a></li> 13 - <li><a href="/">::</a></li> 14 - <li><a href="/hello.html">::hello.html</a></li> 15 - <li><a href="/cat.png">::cat.png</a></li> 16 - <li><a href="/styles.css">::styles.css</a></li> 17 - </ul> 18 - </body> 19 - </html>
-3
server/blobs/did:web:invalid/styles
··· 1 - :root { 2 - color-scheme: light-dark; 3 - }
-11
server/blobs/did:web:invalid/this_is_a_cid
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>404</title> 7 - </head> 8 - <body> 9 - <h1>404</h1> 10 - </body> 11 - </html>
+39
server/src/backfill/blob.ts
··· 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 + import { getPds } from "../utils.ts"; 3 + 4 + export default async function ( 5 + did: `did:${"plc" | "web"}:${string}`, 6 + cid: string 7 + ): Promise<Blob> { 8 + const pds = await getPds(did); 9 + if (!pds) throw "PDS not found"; 10 + 11 + const { data, ok } = await new Client({ 12 + handler: simpleFetchHandler({ service: pds }), 13 + }).get("com.atproto.sync.getBlob", { 14 + params: { 15 + did, 16 + cid, 17 + }, 18 + as: "blob", 19 + }); 20 + 21 + if (!ok) throw `${data.error}`; 22 + 23 + // check for CSAM etc here 24 + 25 + await Deno.mkdir(`./blobs/${did}`, { recursive: true }) 26 + .then(async () => 27 + Deno.writeFile(`./blobs/${did}/${cid}`, await data.bytes(), { 28 + createNew: true, 29 + }) 30 + ) 31 + .catch((err) => { 32 + if (err instanceof Error) { 33 + err.cause = "Deno.writefile"; 34 + } 35 + throw err; 36 + }); 37 + 38 + return data; 39 + }
+19 -8
server/src/routes/user.ts
··· 6 6 } from "@atcute/identity-resolver"; 7 7 import { and, eq } from "drizzle-orm"; 8 8 import { routes } from "../db/schema.ts"; 9 - import { type db, ROOT_DOMAIN } from "../utils.ts"; 9 + import { type db, isDid, ROOT_DOMAIN } from "../utils.ts"; 10 10 import ascii from "./ascii.txt" with { type: "text" }; 11 + import storeBlob from "../backfill/blob.ts"; 11 12 12 13 const handleResolver = new CompositeHandleResolver({ 13 14 strategy: "race", ··· 89 90 }, 90 91 }); 91 92 } catch { 92 - return new Response(`${ascii} 93 + if (!isDid(db_res.did)) 94 + return new Response(`${ascii} 93 95 94 - This page isn't stored in the CDN. 95 - TODO: 96 - Fetch the content from the pds, 97 - check its hash, 98 - serve it if not known as illegal, 99 - store in cdn if doesnt take account over fs limit 96 + ${db_res.did} is not a valid DID. This account could not be resolved 100 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 + 109 + This page couldn't be resolved. Either the blob does not exist, or contained illegal content. 110 + `); 111 + } 101 112 } 102 113 }