WIP PWA for Grain
0
fork

Configure Feed

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

feat: add searchGalleries and searchProfiles API methods

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

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

+96
+96
src/services/grain-api.js
··· 99 99 }; 100 100 } 101 101 102 + async searchGalleries(query, { first = 10, after = null } = {}) { 103 + const gqlQuery = ` 104 + query SearchGalleries($query: String!, $first: Int, $after: String) { 105 + socialGrainGallery( 106 + first: $first 107 + after: $after 108 + where: { title: { contains: $query } } 109 + sortBy: [{ field: createdAt, direction: DESC }] 110 + ) { 111 + edges { 112 + node { 113 + uri 114 + title 115 + description 116 + createdAt 117 + actorHandle 118 + socialGrainActorProfileByDid { 119 + avatar { url } 120 + displayName 121 + } 122 + socialGrainGalleryItemViaGallery(first: 10, sortBy: [{ field: position, direction: ASC }]) { 123 + edges { 124 + node { 125 + itemResolved { 126 + ... on SocialGrainPhoto { 127 + uri 128 + alt 129 + aspectRatio { width height } 130 + photo { url } 131 + } 132 + } 133 + } 134 + } 135 + } 136 + socialGrainFavoriteViaSubject { 137 + totalCount 138 + } 139 + socialGrainCommentViaSubject { 140 + totalCount 141 + } 142 + } 143 + } 144 + pageInfo { 145 + hasNextPage 146 + endCursor 147 + } 148 + } 149 + } 150 + `; 151 + 152 + const response = await this.#execute(gqlQuery, { query, first, after }); 153 + return this.#transformTimelineResponse(response); 154 + } 155 + 156 + async searchProfiles(query, { first = 20, after = null } = {}) { 157 + const gqlQuery = ` 158 + query SearchProfiles($query: String!, $first: Int, $after: String) { 159 + socialGrainActorProfile( 160 + first: $first 161 + after: $after 162 + where: { actorHandle: { contains: $query } } 163 + ) { 164 + edges { 165 + node { 166 + actorHandle 167 + displayName 168 + description 169 + avatar { url } 170 + } 171 + } 172 + pageInfo { 173 + hasNextPage 174 + endCursor 175 + } 176 + } 177 + } 178 + `; 179 + 180 + const response = await this.#execute(gqlQuery, { query, first, after }); 181 + const connection = response.data?.socialGrainActorProfile; 182 + 183 + if (!connection) return { profiles: [], pageInfo: { hasNextPage: false } }; 184 + 185 + const profiles = connection.edges.map(edge => ({ 186 + handle: edge.node.actorHandle, 187 + displayName: edge.node.displayName || '', 188 + description: edge.node.description || '', 189 + avatarUrl: edge.node.avatar?.url || '' 190 + })); 191 + 192 + return { 193 + profiles, 194 + pageInfo: connection.pageInfo 195 + }; 196 + } 197 + 102 198 async #execute(query, variables = {}) { 103 199 const response = await fetch(this.#endpoint, { 104 200 method: 'POST',