WIP PWA for Grain
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add getGalleryDetail method to API service

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+97
+97
src/services/grain-api.js
··· 187 187 galleries 188 188 }; 189 189 } 190 + 191 + async getGalleryDetail(handle, rkey) { 192 + const query = ` 193 + query GetGalleryDetail($handle: String!, $rkey: String!) { 194 + socialGrainGallery( 195 + first: 1 196 + where: { actorHandle: { eq: $handle }, rkey: { eq: $rkey } } 197 + ) { 198 + edges { 199 + node { 200 + uri 201 + did 202 + actorHandle 203 + title 204 + description 205 + createdAt 206 + appBskyActorProfileByDid { 207 + displayName 208 + avatar { url(preset: "avatar") } 209 + } 210 + socialGrainGalleryItemViaGallery(first: 50) { 211 + edges { 212 + node { 213 + itemResolved { 214 + ... on SocialGrainPhoto { 215 + uri 216 + alt 217 + aspectRatio { width height } 218 + photo { url(preset: "feed_fullsize") } 219 + } 220 + } 221 + } 222 + } 223 + } 224 + socialGrainFavoriteViaSubject { 225 + totalCount 226 + } 227 + socialGrainCommentViaSubject( 228 + first: 20 229 + sortBy: [{ field: createdAt, direction: ASC }] 230 + ) { 231 + totalCount 232 + edges { 233 + node { 234 + text 235 + createdAt 236 + actorHandle 237 + } 238 + } 239 + } 240 + } 241 + } 242 + } 243 + } 244 + `; 245 + 246 + const response = await this.#execute(query, { handle, rkey }); 247 + const galleryNode = response.data?.socialGrainGallery?.edges?.[0]?.node; 248 + 249 + if (!galleryNode) { 250 + throw new Error('Gallery not found'); 251 + } 252 + 253 + const profile = galleryNode.appBskyActorProfileByDid; 254 + 255 + const photos = galleryNode.socialGrainGalleryItemViaGallery?.edges 256 + ?.map(edge => edge.node?.itemResolved) 257 + ?.filter(Boolean) 258 + ?.map(photo => ({ 259 + uri: photo.uri, 260 + url: photo.photo?.url || '', 261 + alt: photo.alt || '', 262 + aspectRatio: photo.aspectRatio 263 + ? photo.aspectRatio.width / photo.aspectRatio.height 264 + : 1 265 + })) || []; 266 + 267 + const comments = galleryNode.socialGrainCommentViaSubject?.edges?.map(edge => ({ 268 + text: edge.node.text, 269 + createdAt: edge.node.createdAt, 270 + actorHandle: edge.node.actorHandle 271 + })) || []; 272 + 273 + return { 274 + uri: galleryNode.uri, 275 + title: galleryNode.title, 276 + description: galleryNode.description, 277 + createdAt: galleryNode.createdAt, 278 + handle: galleryNode.actorHandle, 279 + displayName: profile?.displayName || '', 280 + avatarUrl: profile?.avatar?.url || '', 281 + photos, 282 + favoriteCount: galleryNode.socialGrainFavoriteViaSubject?.totalCount || 0, 283 + commentCount: galleryNode.socialGrainCommentViaSubject?.totalCount || 0, 284 + comments 285 + }; 286 + } 190 287 } 191 288 192 289 export const grainApi = new GrainApiService();