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

server: discard unused blobs and dont store files which would take the blob storage for a user over ~0.25gb

vielle.dev d4b48394 2910f4ff

verified
Changed files
+27 -18
server
+10 -12
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}`, ··· 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;
··· 1 import { Client, simpleFetchHandler } from "@atcute/client"; 2 + import { getPds, MAX_SITE_SIZE } from "../utils.ts"; 3 4 export default async function ( 5 did: `did:${"plc" | "web"}:${string}`, ··· 22 23 // check for CSAM etc here 24 25 + await Deno.mkdir(`./blobs/${did}`, { recursive: true }); 26 + let size = 0; 27 + for await (const file of Deno.readDir(`./blobs/${did}`)) { 28 + size += (await Deno.stat(`./blobs/${did}/${file.name}`)).size; 29 + } 30 + // only write if total stays below max site size 31 + if (size + data.size <= MAX_SITE_SIZE) 32 + await Deno.writeFile(`./blobs/${did}/${cid}`, await data.bytes(), { 33 + createNew: true, 34 }); 35 36 return data;
+16 -6
server/src/backfill/new-records.ts
··· 143 mime: data.value.page.blob.mimeType, 144 }); 145 146 - console.log("wrote route"); 147 - 148 continue; 149 } 150 case "delete": { 151 - const id = ( 152 await db 153 .select({ 154 id: routes.id, 155 }) 156 .from(routes) 157 .where( ··· 161 ) 162 ) 163 ).at(0); 164 - if (!id) continue; 165 166 - await db.delete(routes).where(eq(routes.id, id.id)); 167 - console.log("deleted route"); 168 169 continue; 170 }
··· 143 mime: data.value.page.blob.mimeType, 144 }); 145 146 continue; 147 } 148 case "delete": { 149 + const deleted = ( 150 await db 151 .select({ 152 id: routes.id, 153 + cid: routes.blob_cid, 154 }) 155 .from(routes) 156 .where( ··· 160 ) 161 ) 162 ).at(0); 163 + if (!deleted) continue; 164 + 165 + await db.delete(routes).where(eq(routes.id, deleted.id)); 166 167 + const cidInUse = 168 + ( 169 + await db 170 + .select({}) 171 + .from(routes) 172 + .where(eq(routes.blob_cid, deleted.cid)) 173 + ).length > 0; 174 + 175 + // purge cid if not in use 176 + if (!cidInUse) 177 + Deno.remove(`./blobs/${payload.repo}/${deleted.cid}`); 178 179 continue; 180 }
+1
server/src/utils.ts
··· 13 14 export const ROOT_DOMAIN = Deno.env.get("HOSTNAME") || "localhost"; 15 export const PORT = Number(Deno.env.get("PORT")) || 80; 16 17 export const SUBDOMAIN_REGEX = new RegExp(`.+(?=\\.${ROOT_DOMAIN}$)`, "gm"); 18
··· 13 14 export const ROOT_DOMAIN = Deno.env.get("HOSTNAME") || "localhost"; 15 export const PORT = Number(Deno.env.get("PORT")) || 80; 16 + export const MAX_SITE_SIZE = Number(Deno.env.get("MAX_SITE_SIZE")) || 250000000; 17 18 export const SUBDOMAIN_REGEX = new RegExp(`.+(?=\\.${ROOT_DOMAIN}$)`, "gm"); 19