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

lexicons, server: make pages have an open union

options are:
- blob: object with blob record which is a blob itself

in future could be used for things like xml-ed xrpc
calls with processors and rss'd xml'd xrpc calls (for
ex: leaflet pubs as an rss feed)

Changed files
+79 -49
lexicons
server
src
lexicons
types
dev
atcities
+13 -2
lexicons/dev.atcities.route.json
··· 11 11 "type": "object", 12 12 "properties": { 13 13 "page": { 14 - "type": "blob", 15 - "accept": ["*/*"] 14 + "type": "union", 15 + "refs": ["#blob"] 16 16 } 17 17 }, 18 18 "required": ["page"] 19 19 } 20 + }, 21 + 22 + "blob": { 23 + "type": "object", 24 + "properties": { 25 + "blob": { 26 + "type": "blob", 27 + "accept": ["*/*"] 28 + } 29 + }, 30 + "required": ["blob"] 20 31 } 21 32 } 22 33 }
+13 -1
server/src/lexicons/types/dev/atcities/route.ts
··· 2 2 import * as v from "@atcute/lexicons/validations"; 3 3 import type {} from "@atcute/lexicons/ambient"; 4 4 5 + const _blobSchema = /*#__PURE__*/ v.object({ 6 + $type: /*#__PURE__*/ v.optional( 7 + /*#__PURE__*/ v.literal("dev.atcities.route#blob"), 8 + ), 9 + blob: /*#__PURE__*/ v.blob(), 10 + }); 5 11 const _mainSchema = /*#__PURE__*/ v.record( 6 12 /*#__PURE__*/ v.string(), 7 13 /*#__PURE__*/ v.object({ 8 14 $type: /*#__PURE__*/ v.literal("dev.atcities.route"), 9 - page: /*#__PURE__*/ v.blob(), 15 + get page() { 16 + return /*#__PURE__*/ v.variant([blobSchema]); 17 + }, 10 18 }), 11 19 ); 12 20 21 + type blob$schematype = typeof _blobSchema; 13 22 type main$schematype = typeof _mainSchema; 14 23 24 + export interface blobSchema extends blob$schematype {} 15 25 export interface mainSchema extends main$schematype {} 16 26 27 + export const blobSchema = _blobSchema as blobSchema; 17 28 export const mainSchema = _mainSchema as mainSchema; 18 29 30 + export interface Blob extends v.InferInput<typeof blobSchema> {} 19 31 export interface Main extends v.InferInput<typeof mainSchema> {} 20 32 21 33 declare module "@atcute/lexicons/ambient" {
+53 -46
server/src/user.ts
··· 169 169 } 170 170 // unless its a 404, internal error 171 171 default: 172 - throw "Internal Error"; 172 + throw ( 173 + "Internal Error: got error fetching record: " + record_data.error 174 + ); 173 175 } 174 176 } 175 177 178 + console.log(record_data.value); 176 179 if (is(DevAtcitiesRoute.mainSchema, record_data.value)) { 177 - const { ok: blob_ok, data: blob_data } = await client.get( 178 - "com.atproto.sync.getBlob", 179 - { 180 - params: { 181 - did, 182 - cid: 183 - "ref" in record_data.value.page 184 - ? record_data.value.page.ref.$link 185 - : record_data.value.page.cid, 186 - }, 187 - as: "stream", 188 - } 189 - ); 180 + switch (record_data.value.page.$type) { 181 + case "dev.atcities.route#blob": { 182 + const { ok: blob_ok, data: blob_data } = await client.get( 183 + "com.atproto.sync.getBlob", 184 + { 185 + params: { 186 + did, 187 + cid: 188 + "ref" in record_data.value.page.blob 189 + ? record_data.value.page.blob.ref.$link 190 + : record_data.value.page.blob.cid, 191 + }, 192 + as: "stream", 193 + } 194 + ); 190 195 191 - // possible errors include: 192 - // - request issue 193 - // - 404 not found 194 - // - account takedown 195 - // in all cases thats not recoverable 196 - // so throw out and take the happy path 197 - if (!blob_ok) { 198 - if (blob_data.error === "BlobNotFound") { 199 - // 404 error so try load 404 page 200 - if (route !== "404") { 201 - const r404 = await getRoute(did, pds, "404"); 202 - return new Response(r404.body, { 203 - status: 404, 204 - statusText: "Not Found", 205 - headers: r404.headers, 206 - }); 196 + // possible errors include: 197 + // - request issue 198 + // - 404 not found 199 + // - account takedown 200 + // in all cases thats not recoverable 201 + // so throw out and take the happy path 202 + if (!blob_ok) { 203 + if (blob_data.error === "BlobNotFound") { 204 + // 404 error so try load 404 page 205 + if (route !== "404") { 206 + const r404 = await getRoute(did, pds, "404"); 207 + return new Response(r404.body, { 208 + status: 404, 209 + statusText: "Not Found", 210 + headers: r404.headers, 211 + }); 212 + } 213 + return new Response("Could not load page.", { 214 + status: 404, 215 + statusText: "Not Found", 216 + }); 217 + } else 218 + throw "Internal Error: Error fetching blob: " + blob_data.error; 207 219 } 208 - return new Response("Could not load page.", { 209 - status: 404, 210 - statusText: "Not Found", 220 + 221 + return new Response(blob_data, { 222 + headers: { 223 + "Content-Type": record_data.value.page.blob.mimeType, 224 + }, 211 225 }); 212 - } else throw "Internal Error"; 226 + } 213 227 } 214 - 215 - return new Response(blob_data, { 216 - headers: { 217 - "Content-Type": record_data.value.page.mimeType, 218 - }, 219 - }); 220 - 221 - // isnt valid data so throw exception 222 - } else 223 - return new Response( 224 - "Malformed record for at://" + did + "/dev.atcities.route/" + targetRkey 225 - ); 228 + } 229 + // isnt valid data so throw exception 230 + return new Response( 231 + "Malformed record for at://" + did + "/dev.atcities.route/" + targetRkey 232 + ); 226 233 } catch (e) { 227 234 console.error(e); 228 235 return new Response("Something went wrong loading this route", {