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

get indexes working

+4 -4
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.39", 5 + "@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.40", 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", ··· 20 20 "start": "deno run -A ./src/main.tsx", 21 21 "dev": "deno run \"dev:*\"", 22 22 "build": "deno task build:static && deno task build:tailwind && deno task build:fonts", 23 - "build:static": "deno run -A jsr:@bigmoves/bff-cli@0.3.0-beta.37 build src/static/mod.ts", 23 + "build:static": "deno run -A jsr:@bigmoves/bff-cli@0.3.0-beta.40 build src/static/mod.ts", 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 - "dev:build": "DEV=true deno -A --watch=src/static/ jsr:@bigmoves/bff-cli@0.3.0-beta.37 build src/static/mod.ts", 26 + "dev:build": "DEV=true deno -A --watch=src/static/ jsr:@bigmoves/bff-cli@0.3.0-beta.40 build src/static/mod.ts", 27 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 - "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", 30 + "sync": "deno run -A --env=.env jsr:@bigmoves/bff-cli@0.3.0-beta.40 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 --collection-key-map=\"{\"social.grain.favorite\":[\"subject\"],\"social.grain.graph.follow\":[\"subject\"],\"social.grain.gallery.item\":[\"gallery\",\"item\"],\"social.grain.photo.exif\":[\"photo\"]}\"", 31 31 "codegen": "deno run -A jsr:@bigmoves/bff-cli@0.3.0-beta.37 lexgen" 32 32 }, 33 33 "compilerOptions": {
+4 -4
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.39": "0.3.0-beta.39", 5 + "jsr:@bigmoves/bff@0.3.0-beta.40": "0.3.0-beta.40", 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", ··· 92 92 "npm:jose" 93 93 ] 94 94 }, 95 - "@bigmoves/bff@0.3.0-beta.39": { 96 - "integrity": "6841d6b04aa91f5c79b8530af1d2721c0f1bf5b4038270da528121f3ab57765f", 95 + "@bigmoves/bff@0.3.0-beta.40": { 96 + "integrity": "826055f189da5fafb53011ad393e44f605aa5bfb7bc5b016b8663e5c922d7abd", 97 97 "dependencies": [ 98 98 "jsr:@bigmoves/atproto-oauth-client", 99 99 "jsr:@std/assert@^1.0.13", ··· 2021 2021 }, 2022 2022 "workspace": { 2023 2023 "dependencies": [ 2024 - "jsr:@bigmoves/bff@0.3.0-beta.39", 2024 + "jsr:@bigmoves/bff@0.3.0-beta.40", 2025 2025 "jsr:@std/http@^1.0.17", 2026 2026 "jsr:@std/path@^1.0.9", 2027 2027 "npm:@atproto/syntax@0.4",
+35 -5
src/lib/gallery.ts
··· 110 110 const labels = ctx.indexService.queryLabels({ 111 111 subjects: [gallery.uri], 112 112 }); 113 + 113 114 const favs = getGalleryFavs(gallery.uri, ctx); 114 - const viewerFav = favs.find((fav) => fav.did === ctx.currentUser?.did); 115 + 116 + let viewerFav: string | undefined = undefined; 117 + if (ctx.currentUser?.did) { 118 + const fav = getGalleryFav(ctx.currentUser?.did, gallery.uri, ctx); 119 + if (fav) { 120 + viewerFav = fav.uri; 121 + } 122 + } 123 + 115 124 return galleryToView({ 116 125 record: gallery, 117 126 creator: profile, 118 127 items: galleryPhotosMap.get(gallery.uri) ?? [], 119 128 labels, 120 - favCount: favs.length, 129 + favCount: favs, 121 130 viewerState: { 122 - fav: viewerFav ? viewerFav.uri : undefined, 131 + fav: viewerFav, 123 132 }, 124 133 }); 125 134 } ··· 147 156 148 157 export function getGalleryFavs(galleryUri: string, ctx: BffContext) { 149 158 const atUri = new AtUri(galleryUri); 150 - const results = ctx.indexService.getRecords<WithBffMeta<Favorite>>( 159 + const count = ctx.indexService.countRecords( 151 160 "social.grain.favorite", 152 161 { 153 162 where: [ ··· 158 167 ], 159 168 }, 160 169 ); 161 - return results.items; 170 + return count; 171 + } 172 + 173 + export function getGalleryFav( 174 + did: string, 175 + galleryUri: string, 176 + ctx: BffContext, 177 + ) { 178 + const atUri = new AtUri(galleryUri); 179 + const { items: favs } = ctx.indexService.getRecords<WithBffMeta<Favorite>>( 180 + "social.grain.favorite", 181 + { 182 + where: [ 183 + { 184 + field: "subject", 185 + equals: `at://${atUri.hostname}/social.grain.gallery/${atUri.rkey}`, 186 + }, 187 + { field: "did", equals: did }, 188 + ], 189 + }, 190 + ); 191 + return favs[0]; 162 192 } 163 193 164 194 export function galleryToView({
+13 -3
src/lib/timeline.ts
··· 10 10 import { getActorProfile } from "./actor.ts"; 11 11 import { 12 12 galleryToView, 13 + getGalleryFav, 13 14 getGalleryFavs, 14 15 getGalleryItemsAndPhotos, 15 16 } from "./gallery.ts"; ··· 79 80 const labels = ctx.indexService.queryLabels({ 80 81 subjects: [gallery.uri], 81 82 }); 83 + 82 84 const favs = getGalleryFavs(gallery.uri, ctx); 83 - const viewerFav = favs.find((fav) => fav.did === ctx.currentUser?.did); 85 + 86 + let viewerFav: string | undefined = undefined; 87 + if (ctx.currentUser?.did) { 88 + const fav = getGalleryFav(ctx.currentUser?.did, gallery.uri, ctx); 89 + if (fav) { 90 + viewerFav = fav.uri; 91 + } 92 + } 93 + 84 94 const galleryView = galleryToView({ 85 95 record: gallery, 86 96 creator: profile, 87 97 items: galleryPhotos, 88 98 labels, 89 - favCount: favs.length, 99 + favCount: favs, 90 100 viewerState: { 91 - fav: viewerFav ? viewerFav.uri : undefined, 101 + fav: viewerFav, 92 102 }, 93 103 }); 94 104
+6
src/main.tsx
··· 41 41 "sh.tangled.actor.profile", 42 42 "sh.tangled.graph.follow", 43 43 ], 44 + collectionKeyMap: { 45 + "social.grain.favorite": ["subject"], 46 + "social.grain.graph.follow": ["subject"], 47 + "social.grain.gallery.item": ["gallery", "item"], 48 + "social.grain.photo.exif": ["photo"], 49 + }, 44 50 lexicons, 45 51 rootElement: Root, 46 52 onError,
+7 -3
sync.sh
··· 2 2 3 3 # Helpful when running local-infra. Specify the repos you've created on a local pds instance. 4 4 5 - REPOS="did:plc:yyz2m2gxnbaxoru2sepbltxv" 5 + DB="backup-2025-06-18.db" 6 + REPOS="" 6 7 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 8 EXTERNAL_COLLECTIONS="app.bsky.actor.profile,app.bsky.graph.follow,sh.tangled.graph.follow,sh.tangled.actor.profile" 9 + COLLECTION_KEY_MAP='{"social.grain.favorite":["subject"],"social.grain.graph.follow":["subject"],"social.grain.gallery.item":["gallery","item"],"social.grain.photo.exif":["photo"]}' 8 10 9 - deno run -A --env=.env jsr:@bigmoves/bff-cli@0.3.0-beta.37 sync \ 11 + deno run -A --env=.env jsr:@bigmoves/bff-cli@0.3.0-beta.40 sync \ 12 + --db="$DB" \ 10 13 --repos="$REPOS" \ 11 14 --collections="$COLLECTIONS" \ 12 - --external-collections="$EXTERNAL_COLLECTIONS" 15 + --external-collections="$EXTERNAL_COLLECTIONS" \ 16 + --collection-key-map="$COLLECTION_KEY_MAP"