grain.social is a photo sharing platform built on atproto.

better handling of app labelers and running local infra, add sync.sh script for when using local-infra

+2 -2
deno.json
··· 2 2 "imports": { 3 3 "$lexicon/": "./__generated__/", 4 4 "@atproto/syntax": "npm:@atproto/syntax@^0.4.0", 5 - "@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.37", 5 + "@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.38", 6 6 "@std/http": "jsr:@std/http@^1.0.17", 7 7 "@std/path": "jsr:@std/path@^1.0.9", 8 8 "@tailwindcss/cli": "npm:@tailwindcss/cli@^4.1.4", ··· 24 24 "build:tailwind": "deno run -A --node-modules-dir npm:@tailwindcss/cli -i ./src/input.css -o ./build/styles.css --minify", 25 25 "build:fonts": "rm -rf ./build/fonts && cp -r ./static/fonts/. ./build/fonts", 26 26 "dev:build": "DEV=true deno -A --watch=src/static/ jsr:@bigmoves/bff-cli@0.3.0-beta.37 build src/static/mod.ts", 27 - "dev:server": "deno run -A --watch ./src/main.tsx", 27 + "dev:server": "deno run -A --env-file --watch ./src/main.tsx", 28 28 "dev:tailwind": "deno run -A --node-modules-dir npm:@tailwindcss/cli -i ./src/input.css -o ./build/styles.css --watch", 29 29 "dev:fonts": "rm -rf ./build/fonts && cp -r ./static/fonts/. ./build/fonts", 30 30 "sync": "deno run -A --env=.env jsr:@bigmoves/bff-cli@0.3.0-beta.37 sync --collections=social.grain.gallery,social.grain.actor.profile,social.grain.photo,social.grain.favorite,social.grain.gallery.item,social.grain.graph.follow,social.grain.photo.exif --external-collections=app.bsky.actor.profile,app.bsky.graph.follow,sh.tangled.graph.follow,sh.tangled.actor.profile",
+21 -44
deno.lock
··· 2 2 "version": "5", 3 3 "specifiers": { 4 4 "jsr:@bigmoves/atproto-oauth-client@0.2": "0.2.0", 5 - "jsr:@bigmoves/bff@0.3.0-beta.37": "0.3.0-beta.37", 5 + "jsr:@bigmoves/bff@0.3.0-beta.38": "0.3.0-beta.38", 6 6 "jsr:@deno/gfm@0.10": "0.10.0", 7 7 "jsr:@denosaurs/emoji@0.3": "0.3.1", 8 8 "jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1", ··· 14 14 "jsr:@std/cli@^1.0.16": "1.0.20", 15 15 "jsr:@std/cli@^1.0.20": "1.0.20", 16 16 "jsr:@std/data-structures@^1.0.6": "1.0.7", 17 + "jsr:@std/dotenv@*": "0.225.5", 17 18 "jsr:@std/encoding@^1.0.10": "1.0.10", 18 19 "jsr:@std/encoding@^1.0.5": "1.0.10", 19 20 "jsr:@std/fmt@^1.0.8": "1.0.8", ··· 32 33 "jsr:@std/streams@^1.0.10": "1.0.10", 33 34 "jsr:@std/testing@^1.0.11": "1.0.11", 34 35 "npm:@atproto-labs/handle-resolver-node@~0.1.14": "0.1.16", 35 - "npm:@atproto-labs/simple-store@~0.1.2": "0.1.2", 36 - "npm:@atproto/api@~0.15.7": "0.15.14", 36 + "npm:@atproto/api@~0.15.7": "0.15.15", 37 37 "npm:@atproto/common@~0.4.10": "0.4.11", 38 38 "npm:@atproto/identity@~0.4.7": "0.4.8", 39 39 "npm:@atproto/jwk@0.1.4": "0.1.4", 40 40 "npm:@atproto/lexicon@*": "0.4.11", 41 41 "npm:@atproto/lexicon@0.4.11": "0.4.11", 42 42 "npm:@atproto/lexicon@~0.4.11": "0.4.11", 43 - "npm:@atproto/oauth-client@~0.3.13": "0.3.21", 44 - "npm:@atproto/oauth-types@~0.2.4": "0.2.8", 43 + "npm:@atproto/oauth-client@~0.3.13": "0.3.22", 45 44 "npm:@atproto/syntax@0.4": "0.4.0", 46 45 "npm:@atproto/xrpc-server@*": "0.7.19", 47 - "npm:@atproto/xrpc-server@0.7.18": "0.7.18", 48 46 "npm:@tailwindcss/cli@*": "4.1.9", 49 47 "npm:@tailwindcss/cli@^4.0.12": "4.1.9", 50 48 "npm:@tailwindcss/cli@^4.1.3": "4.1.9", ··· 64 62 "npm:marked-footnote@^1.2.0": "1.2.4_marked@12.0.2", 65 63 "npm:marked-gfm-heading-id@^3.1.0": "3.2.0_marked@12.0.2", 66 64 "npm:marked@12": "12.0.2", 67 - "npm:multiformats@*": "13.3.7", 65 + "npm:multiformats@*": "9.9.0", 68 66 "npm:multiformats@^13.3.2": "13.3.7", 69 67 "npm:popmotion@^11.0.5": "11.0.5", 70 68 "npm:preact-render-to-string@^6.5.13": "6.5.13_preact@10.26.9", ··· 83 81 "integrity": "5c3ca124dd52eff51dace83790779ebe48c4b41559b799e16c8750bd415f2124", 84 82 "dependencies": [ 85 83 "npm:@atproto-labs/handle-resolver-node", 86 - "npm:@atproto-labs/simple-store", 87 84 "npm:@atproto/jwk", 88 85 "npm:@atproto/oauth-client", 89 - "npm:@atproto/oauth-types", 90 86 "npm:jose" 91 87 ] 92 88 }, 93 - "@bigmoves/bff@0.3.0-beta.37": { 94 - "integrity": "0b6203729c667642bfaa7481bf0fdddb55050b77db9dc6d3857f5a96548a6e3f", 89 + "@bigmoves/bff@0.3.0-beta.38": { 90 + "integrity": "74b8b77f451a00b16c9a43ce776bdac87753429bf2d15bf6c81b5eda1e2f9e0e", 95 91 "dependencies": [ 96 92 "jsr:@bigmoves/atproto-oauth-client", 97 93 "jsr:@std/assert@^1.0.13", ··· 102 98 "npm:@atproto/api", 103 99 "npm:@atproto/common", 104 100 "npm:@atproto/identity", 105 - "npm:@atproto/lexicon@0.4.11", 106 101 "npm:@atproto/lexicon@~0.4.11", 107 102 "npm:@atproto/oauth-client", 108 103 "npm:@atproto/syntax", 109 - "npm:@atproto/xrpc-server@0.7.18", 110 104 "npm:clsx", 111 105 "npm:multiformats@^13.3.2", 112 106 "npm:preact", ··· 161 155 "@std/data-structures@1.0.7": { 162 156 "integrity": "16932d2c8d281f65eaaa2209af2473209881e33b1ced54cd1b015e7b4cdbb0d2" 163 157 }, 158 + "@std/dotenv@0.225.5": { 159 + "integrity": "9ce6f9d0ec3311f74a32535aa1b8c62ed88b1ab91b7f0815797d77a6f60c922f" 160 + }, 164 161 "@std/encoding@1.0.10": { 165 162 "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" 166 163 }, ··· 230 227 "dependencies": [ 231 228 "@atproto-labs/fetch", 232 229 "@atproto-labs/pipe", 233 - "@atproto-labs/simple-store@0.2.0", 230 + "@atproto-labs/simple-store", 234 231 "@atproto-labs/simple-store-memory", 235 232 "@atproto/did", 236 233 "zod" ··· 262 259 "@atproto-labs/handle-resolver@0.1.8": { 263 260 "integrity": "sha512-Y0ckccoCGDo/3g4thPkgp9QcORmc+qqEaCBCYCZYtfLIQp4775u22wd+4fyEyJP4DqoReKacninkICgRGfs3dQ==", 264 261 "dependencies": [ 265 - "@atproto-labs/simple-store@0.2.0", 262 + "@atproto-labs/simple-store", 266 263 "@atproto-labs/simple-store-memory", 267 264 "@atproto/did", 268 265 "zod" 269 266 ] 270 267 }, 271 - "@atproto-labs/identity-resolver@0.1.17": { 272 - "integrity": "sha512-EaH9Lm8M85IKRx+oWZ4tppYRVH8u+MYpEz1kjzYeM3ttZ2xcqKVmYHiOIgd5YPCVV2EIfXKnlM4soHQ+rZ1c6A==", 268 + "@atproto-labs/identity-resolver@0.1.18": { 269 + "integrity": "sha512-DArYXP1hzZJIBcojun0CWEF+TjAhlGKcVq/RwLiGfY1mKq2yPjCiXyHj+5L0+z9jBSZiAB7L65JgcjI2+MFiRg==", 273 270 "dependencies": [ 274 271 "@atproto-labs/did-resolver", 275 272 "@atproto-labs/handle-resolver", ··· 282 279 "@atproto-labs/simple-store-memory@0.1.3": { 283 280 "integrity": "sha512-jkitT9+AtU+0b28DoN92iURLaCt/q/q4yX8q6V+9LSwYlUTqKoj/5NFKvF7x6EBuG+gpUdlcycbH7e60gjOhRQ==", 284 281 "dependencies": [ 285 - "@atproto-labs/simple-store@0.2.0", 282 + "@atproto-labs/simple-store", 286 283 "lru-cache" 287 284 ] 288 - }, 289 - "@atproto-labs/simple-store@0.1.2": { 290 - "integrity": "sha512-9vTNvyPPBs44tKVFht16wGlilW8u4wpEtKwLkWbuNEh3h9TTQ8zjVhEoGZh/v73G4Otr9JUOSIq+/5+8OZD2mQ==" 291 285 }, 292 286 "@atproto-labs/simple-store@0.2.0": { 293 287 "integrity": "sha512-0bRbAlI8Ayh03wRwncAMEAyUKtZ+AuTS1jgPrfym1WVOAOiottI/ZmgccqLl6w5MbxVcClNQF7WYGKvGwGoIhA==" 294 288 }, 295 - "@atproto/api@0.15.14": { 296 - "integrity": "sha512-FHEMAdscG+r2OFcZUIzPyTDpwzRAyinRsIIaTcuqe0MgZWF4CEGNAKPos0IbecBzMxTOzUHE18dQDKhoXMdgvg==", 289 + "@atproto/api@0.15.15": { 290 + "integrity": "sha512-Wn8jv76pCvffnkNj68w0CGZ3PT4DJGM8DUZnYq9kEW2im6jbRBYI0yYrHNhSiE92A5Ox0HjL2jMhalsI2p9VlQ==", 297 291 "dependencies": [ 298 292 "@atproto/common-web", 299 293 "@atproto/lexicon", ··· 370 364 "zod" 371 365 ] 372 366 }, 373 - "@atproto/oauth-client@0.3.21": { 374 - "integrity": "sha512-a+YM3aaOAY8/otlAnYmXC0XO+KLOo2cZGHwVvudTSnzwpps8sxhuELdPFl3JN16fS45TUnkYUIaqBgb9MLpx8w==", 367 + "@atproto/oauth-client@0.3.22": { 368 + "integrity": "sha512-IJYkUSGGklV7tQ0S2+5smh8Xmu5MwfxBUNXMtqiooeU2nj+UcNk3/b0nE4MS05JNfwh2BXgHv3P8hrhVG2+RAA==", 375 369 "dependencies": [ 376 370 "@atproto-labs/did-resolver", 377 371 "@atproto-labs/fetch", 378 372 "@atproto-labs/handle-resolver", 379 373 "@atproto-labs/identity-resolver", 380 - "@atproto-labs/simple-store@0.2.0", 374 + "@atproto-labs/simple-store", 381 375 "@atproto-labs/simple-store-memory", 382 376 "@atproto/did", 383 377 "@atproto/jwk@0.2.0", ··· 396 390 }, 397 391 "@atproto/syntax@0.4.0": { 398 392 "integrity": "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA==" 399 - }, 400 - "@atproto/xrpc-server@0.7.18": { 401 - "integrity": "sha512-kjlAsI+UNbbm6AK3Y5Hb4BJ7VQHNKiYYu2kX5vhZJZHO8qfO40GPYYb/2TknZV8IG6fDPBQhUpcDRolI86sgag==", 402 - "dependencies": [ 403 - "@atproto/common", 404 - "@atproto/crypto", 405 - "@atproto/lexicon", 406 - "@atproto/xrpc", 407 - "cbor-x", 408 - "express", 409 - "http-errors", 410 - "mime-types", 411 - "rate-limiter-flexible", 412 - "uint8arrays", 413 - "ws", 414 - "zod" 415 - ] 416 393 }, 417 394 "@atproto/xrpc-server@0.7.19": { 418 395 "integrity": "sha512-YSCl/tU2NDykgDYslFSOYCr96esUgDwncFiADKL59/fyIFPLoT0qY8Uq/budpxUh0qPzjow4HHgVWESOaOpUmA==", ··· 1906 1883 }, 1907 1884 "workspace": { 1908 1885 "dependencies": [ 1909 - "jsr:@bigmoves/bff@0.3.0-beta.37", 1886 + "jsr:@bigmoves/bff@0.3.0-beta.38", 1910 1887 "jsr:@std/http@^1.0.17", 1911 1888 "jsr:@std/path@^1.0.9", 1912 1889 "npm:@atproto/syntax@0.4",
+6 -1
src/app.tsx
··· 55 55 <Layout id="layout"> 56 56 <Layout.Nav 57 57 heading={ 58 - <h1 class="font-['Jersey_20'] text-4xl text-zinc-900 dark:text-white"> 58 + <h1 59 + class="text-4xl text-zinc-900 dark:text-white" 60 + style={{ 61 + fontFamily: "'Jersey 20', sans-serif", 62 + }} 63 + > 59 64 grain 60 65 <sub class="bottom-[0.75rem] text-[1rem]">beta</sub> 61 66 </h1>
+1
src/lib/errors.ts
··· 40 40 }, 41 41 ); 42 42 } 43 + console.error("Unhandled error:", err); 43 44 return errorResponse("Internal Server Error", 500); 44 45 } 45 46
+13 -2
src/lib/moderation.ts
··· 22 22 23 23 for (const label of labels ?? []) { 24 24 const labelSubject = new AtUri(label.uri).hostname; 25 - const labelerAtpData = await ctx.didResolver.resolveAtprotoData(label.src); 25 + let labelerHandle: string | undefined = undefined; 26 + try { 27 + const labelerAtpData = await ctx.didResolver.resolveAtprotoData( 28 + label.src, 29 + ); 30 + labelerHandle = labelerAtpData.handle; 31 + } catch (e) { 32 + console.error( 33 + `Failed to resolve labeler atproto data for ${label.src}: ${e}`, 34 + ); 35 + continue; 36 + } 26 37 // Try labelDefinitions first, then fallback to atprotoLabelValueDefinitions 27 38 let defs = labelDefinitions[label.src]?.labelValueDefinitions?.filter(( 28 39 def, ··· 38 49 return { 39 50 name: enLocale.name, 40 51 description: enLocale.description, 41 - labeledBy: labelerAtpData.handle ?? label.src, 52 + labeledBy: labelerHandle ?? label.src, 42 53 blurs: defs[0].blurs ?? "", 43 54 isMe: labelSubject === did, 44 55 src: label.src,
+2 -6
src/main.tsx
··· 1 1 import { lexicons } from "$lexicon/lexicons.ts"; 2 - import { bff, BffContext, JETSTREAM, oauth, route } from "@bigmoves/bff"; 2 + import { bff, oauth, route } from "@bigmoves/bff"; 3 3 import { Root } from "./app.tsx"; 4 4 import { LoginPage } from "./components/LoginPage.tsx"; 5 5 import { PDS_HOST_URL } from "./env.ts"; ··· 19 19 import { handler as supportHandler } from "./routes/support.tsx"; 20 20 import { handler as timelineHandler } from "./routes/timeline.tsx"; 21 21 import { handler as uploadHandler } from "./routes/upload.tsx"; 22 - import { appStateMiddleware, type State } from "./state.ts"; 22 + import { appStateMiddleware } from "./state.ts"; 23 23 import { onSignedIn } from "./utils.ts"; 24 24 25 25 bff({ ··· 41 41 "sh.tangled.actor.profile", 42 42 "sh.tangled.graph.follow", 43 43 ], 44 - jetstreamUrl: JETSTREAM.WEST_1, 45 44 lexicons, 46 45 rootElement: Root, 47 46 onError, 48 47 middlewares: [ 49 - (_req, ctx: BffContext<State>) => { 50 - return ctx.next(); 51 - }, 52 48 appStateMiddleware, 53 49 oauth({ 54 50 onSignedIn,
+12
sync.sh
··· 1 + #!/usr/bin/env bash 2 + 3 + # Helpful when running local-infra. Specify the repos you've created on a local pds instance. 4 + 5 + REPOS="did:plc:gdvspmipkels2qp43m4czqhp" 6 + COLLECTIONS="social.grain.gallery,social.grain.actor.profile,social.grain.photo,social.grain.favorite,social.grain.gallery.item,social.grain.graph.follow,social.grain.photo.exif" 7 + EXTERNAL_COLLECTIONS="app.bsky.actor.profile,app.bsky.graph.follow,sh.tangled.graph.follow,sh.tangled.actor.profile" 8 + 9 + deno run -A --env=.env jsr:@bigmoves/bff-cli@0.3.0-beta.37 sync \ 10 + --repos="$REPOS" \ 11 + --collections="$COLLECTIONS" \ 12 + --external-collections="$EXTERNAL_COLLECTIONS"