[WIP] A (somewhat barebones) atproto app for creating custom sites without hosting!
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}