Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee
17
fork

Configure Feed

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

fix: recipe suggestions showing user recipes in community

+48 -16
+15 -4
internal/handlers/recipe.go
··· 13 13 "arabica/internal/web/components" 14 14 "arabica/internal/web/pages" 15 15 16 + 16 17 "github.com/rs/zerolog/log" 17 18 ) 18 19 ··· 205 206 recipe.RKey = rkey 206 207 recipe.AuthorDID = ownerDID 207 208 208 - // Resolve brewer reference if present 209 + // Resolve brewer reference: fetch source brewer info, then match 210 + // against the current user's brewers so the returned brewer_rkey 211 + // is usable in the current user's brew form. 209 212 if brewerRef, ok := record.Value["brewerRef"].(string); ok && brewerRef != "" { 210 - if c, err := atproto.ResolveATURI(brewerRef); err == nil { 211 - recipe.BrewerRKey = c.RKey 212 - } 213 213 brewerRKey := atproto.ExtractRKeyFromURI(brewerRef) 214 214 if brewerRKey != "" { 215 215 brewerRecord, err := publicClient.GetRecord(r.Context(), ownerDID, atproto.NSIDBrewer, brewerRKey) ··· 217 217 if brewer, err := atproto.RecordToBrewer(brewerRecord.Value, brewerRecord.URI); err == nil { 218 218 brewer.RKey = brewerRKey 219 219 recipe.BrewerObj = brewer 220 + 221 + // Try to match source brewer to the current user's brewers 222 + if userBrewers, err := store.ListBrewers(r.Context()); err == nil { 223 + candidates := make([]matching.Candidate, len(userBrewers)) 224 + for i, b := range userBrewers { 225 + candidates[i] = matching.Candidate{RKey: b.RKey, Name: b.Name, Type: b.BrewerType} 226 + } 227 + if m := matching.Match(brewer.Name, brewer.BrewerType, candidates); m != nil { 228 + recipe.BrewerRKey = m.RKey 229 + } 230 + } 220 231 } 221 232 } 222 233 }
+5 -1
internal/handlers/suggestions.go
··· 58 58 return 59 59 } 60 60 61 - results, err := suggestions.Search(h.feedIndex, nsid, query, limit) 61 + // Exclude the current user's records from suggestions so they only see 62 + // community records, not their own data echoed back. 63 + excludeDID, _ := atproto.GetAuthenticatedDID(r.Context()) 64 + 65 + results, err := suggestions.Search(h.feedIndex, nsid, query, limit, excludeDID) 62 66 if err != nil { 63 67 log.Error().Err(err).Str("entity", entityType).Str("query", query).Msg("Failed to search suggestions") 64 68 http.Error(w, "Failed to search suggestions", http.StatusInternalServerError)
+12 -1
internal/suggestions/suggestions.go
··· 208 208 // Search searches indexed records for entity suggestions matching a query. 209 209 // It matches against searchable fields, deduplicates using entity-specific 210 210 // keys, and returns results sorted by popularity. 211 - func Search(source RecordSource, collection, query string, limit int) ([]EntitySuggestion, error) { 211 + // If excludeDID is non-empty, records from that DID are excluded from results. 212 + func Search(source RecordSource, collection, query string, limit int, excludeDID ...string) ([]EntitySuggestion, error) { 212 213 if limit <= 0 { 213 214 limit = 10 214 215 } ··· 223 224 return nil, nil 224 225 } 225 226 227 + var skipDID string 228 + if len(excludeDID) > 0 { 229 + skipDID = excludeDID[0] 230 + } 231 + 226 232 records, err := source.ListRecordsByCollection(collection) 227 233 if err != nil { 228 234 return nil, err ··· 237 243 candidates := make(map[string]*candidate) 238 244 239 245 for _, indexed := range records { 246 + // Skip the current user's records so they only see community data 247 + if skipDID != "" && indexed.DID == skipDID { 248 + continue 249 + } 250 + 240 251 var recordData map[string]any 241 252 if err := json.Unmarshal(indexed.Record, &recordData); err != nil { 242 253 continue
+11 -6
static/js/brew-form.js
··· 282 282 '[x-data*="entityType: \'brewer\'"]', 283 283 ); 284 284 if (brewerCombo) { 285 - const brewerName = 286 - (this.dropdownManager?.brewers || []).find( 287 - (b) => 288 - (b.rkey || b.RKey) === recipe.brewer_rkey, 289 - )?.name || ""; 285 + // Try to find matching local brewer by rkey 286 + const localBrewer = (this.dropdownManager?.brewers || []).find( 287 + (b) => (b.rkey || b.RKey) === recipe.brewer_rkey, 288 + ); 289 + const brewerRKey = localBrewer 290 + ? recipe.brewer_rkey 291 + : ""; 292 + const brewerName = localBrewer 293 + ? localBrewer.name || localBrewer.Name || "" 294 + : ""; 290 295 brewerCombo.dispatchEvent( 291 296 new CustomEvent("combo-set", { 292 297 detail: { 293 - rkey: recipe.brewer_rkey || "", 298 + rkey: brewerRKey, 294 299 label: brewerName, 295 300 }, 296 301 bubbles: false,
+5 -4
static/js/combo-select.js
··· 124 124 clearTimeout(this._suggestTimer); 125 125 if (q.length >= 2 && this.suggestEndpoint) { 126 126 this._suggestTimer = setTimeout(() => { 127 - this.fetchSuggestions(q, entities); 127 + this.fetchSuggestions(q); 128 128 }, 400); 129 129 } else { 130 130 this.communityResults = []; 131 131 } 132 132 }, 133 133 134 - async fetchSuggestions(q, entities) { 134 + async fetchSuggestions(q) { 135 135 try { 136 136 const resp = await fetch( 137 137 `${this.suggestEndpoint}?q=${encodeURIComponent(q)}&limit=5`, ··· 139 139 ); 140 140 if (resp.ok) { 141 141 const data = await resp.json(); 142 - // Filter out community results that match any of the user's entities 142 + // Re-read entities fresh from cache (may have loaded since search() ran) 143 + const freshEntities = this.getUserEntities(); 143 144 const allNames = new Set( 144 - entities.map((e) => (e.name || e.Name || "").toLowerCase()), 145 + freshEntities.map((e) => (e.name || e.Name || "").toLowerCase()), 145 146 ); 146 147 this.communityResults = (data || []).filter( 147 148 (s) => !allNames.has((s.name || "").toLowerCase()),