search for standard sites pub-search.waow.tech
search zig blog atproto

feat: add publicationName to search results

Join publications table in all search queries to return
the human-readable publication name alongside basePath.

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

+79 -23
+78 -23
backend/src/server/search.zig
··· 33 path: []const u8 = "", // URL path from record (e.g., "/001") 34 source: []const u8 = "", 35 coverImage: []const u8 = "", 36 }; 37 38 /// Document search result (internal) ··· 48 platform: []const u8, 49 path: []const u8, 50 coverImage: []const u8, 51 52 fn fromRow(row: db.Row) Doc { 53 return .{ ··· 62 .platform = row.text(8), 63 .path = row.text(9), 64 .coverImage = row.text(10), 65 }; 66 } 67 ··· 78 .platform = row.text(8), 79 .path = row.text(9), 80 .coverImage = row.text(10), 81 }; 82 } 83 ··· 94 .platform = self.platform, 95 .path = self.path, 96 .coverImage = self.coverImage, 97 }; 98 } 99 }; ··· 102 \\SELECT d.uri, d.did, d.title, '' as snippet, 103 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 104 \\ d.platform, COALESCE(d.path, '') as path, 105 - \\ COALESCE(d.cover_image, '') as cover_image 106 \\FROM documents d 107 \\JOIN document_tags dt ON d.uri = dt.document_uri 108 \\WHERE dt.tag = :tag 109 \\ORDER BY d.created_at DESC LIMIT 40 ··· 114 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 115 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 116 \\ d.platform, COALESCE(d.path, '') as path, 117 - \\ COALESCE(d.cover_image, '') as cover_image 118 \\FROM documents_fts f 119 \\JOIN documents d ON f.uri = d.uri 120 \\JOIN document_tags dt ON d.uri = dt.document_uri 121 \\WHERE documents_fts MATCH :query AND dt.tag = :tag 122 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 ··· 127 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 128 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 129 \\ d.platform, COALESCE(d.path, '') as path, 130 - \\ COALESCE(d.cover_image, '') as cover_image 131 \\FROM documents_fts f 132 \\JOIN documents d ON f.uri = d.uri 133 \\WHERE documents_fts MATCH :query 134 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 135 ); ··· 139 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 140 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 141 \\ d.platform, COALESCE(d.path, '') as path, 142 - \\ COALESCE(d.cover_image, '') as cover_image 143 \\FROM documents_fts f 144 \\JOIN documents d ON f.uri = d.uri 145 \\WHERE documents_fts MATCH :query AND d.created_at >= :since 146 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 147 ); ··· 151 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 152 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 153 \\ d.platform, COALESCE(d.path, '') as path, 154 - \\ COALESCE(d.cover_image, '') as cover_image 155 \\FROM documents_fts f 156 \\JOIN documents d ON f.uri = d.uri 157 \\WHERE documents_fts MATCH :query AND d.platform = :platform 158 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 159 ); ··· 163 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 164 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 165 \\ d.platform, COALESCE(d.path, '') as path, 166 - \\ COALESCE(d.cover_image, '') as cover_image 167 \\FROM documents_fts f 168 \\JOIN documents d ON f.uri = d.uri 169 \\WHERE documents_fts MATCH :query AND d.platform = :platform AND d.created_at >= :since 170 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 171 ); ··· 174 \\SELECT d.uri, d.did, d.title, '' as snippet, 175 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 176 \\ d.platform, COALESCE(d.path, '') as path, 177 - \\ COALESCE(d.cover_image, '') as cover_image 178 \\FROM documents d 179 \\JOIN document_tags dt ON d.uri = dt.document_uri 180 \\WHERE dt.tag = :tag AND d.platform = :platform 181 \\ORDER BY d.created_at DESC LIMIT 40 ··· 186 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 187 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 188 \\ d.platform, COALESCE(d.path, '') as path, 189 - \\ COALESCE(d.cover_image, '') as cover_image 190 \\FROM documents_fts f 191 \\JOIN documents d ON f.uri = d.uri 192 \\JOIN document_tags dt ON d.uri = dt.document_uri 193 \\WHERE documents_fts MATCH :query AND dt.tag = :tag AND d.platform = :platform 194 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 ··· 198 \\SELECT d.uri, d.did, d.title, '' as snippet, 199 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 200 \\ d.platform, COALESCE(d.path, '') as path, 201 - \\ COALESCE(d.cover_image, '') as cover_image 202 \\FROM documents d 203 \\WHERE d.platform = :platform 204 \\ORDER BY d.created_at DESC LIMIT 40 205 ); ··· 208 \\SELECT d.uri, d.did, d.title, '' as snippet, 209 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 210 \\ d.platform, COALESCE(d.path, '') as path, 211 - \\ COALESCE(d.cover_image, '') as cover_image 212 \\FROM documents d 213 \\WHERE d.did = :author 214 \\ORDER BY d.created_at DESC LIMIT 40 215 ); ··· 218 \\SELECT d.uri, d.did, d.title, '' as snippet, 219 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 220 \\ d.platform, COALESCE(d.path, '') as path, 221 - \\ COALESCE(d.cover_image, '') as cover_image 222 \\FROM documents d 223 \\WHERE d.did = :author AND d.platform = :platform 224 \\ORDER BY d.created_at DESC LIMIT 40 225 ); ··· 233 \\ p.base_path, 234 \\ 1 as has_publication, 235 \\ d.platform, COALESCE(d.path, '') as path, 236 - \\ COALESCE(d.cover_image, '') as cover_image 237 \\FROM documents d 238 \\JOIN publications p ON d.publication_uri = p.uri 239 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 247 \\ p.base_path, 248 \\ 1 as has_publication, 249 \\ d.platform, COALESCE(d.path, '') as path, 250 - \\ COALESCE(d.cover_image, '') as cover_image 251 \\FROM documents d 252 \\JOIN publications p ON d.publication_uri = p.uri 253 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 261 \\ p.base_path, 262 \\ 1 as has_publication, 263 \\ d.platform, COALESCE(d.path, '') as path, 264 - \\ COALESCE(d.cover_image, '') as cover_image 265 \\FROM documents d 266 \\JOIN publications p ON d.publication_uri = p.uri 267 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 275 \\ p.base_path, 276 \\ 1 as has_publication, 277 \\ d.platform, COALESCE(d.path, '') as path, 278 - \\ COALESCE(d.cover_image, '') as cover_image 279 \\FROM documents d 280 \\JOIN publications p ON d.publication_uri = p.uri 281 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 570 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 571 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 572 \\ d.platform, COALESCE(d.path, '') as path, 573 - \\ COALESCE(d.cover_image, '') as cover_image 574 \\FROM documents_fts f 575 \\JOIN documents d ON f.uri = d.uri 576 \\WHERE documents_fts MATCH ? AND d.platform = ? AND (? = '' OR d.did = ?) 577 \\ORDER BY rank LIMIT 40 578 , .{ fts_query, platform, author_val, author_val }); ··· 597 \\SELECT d.uri, d.did, d.title, '' as snippet, 598 \\ d.created_at, d.rkey, p.base_path, 599 \\ 1 as has_publication, d.platform, COALESCE(d.path, '') as path, 600 - \\ COALESCE(d.cover_image, '') as cover_image 601 \\FROM documents d 602 \\JOIN publications p ON d.publication_uri = p.uri 603 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 625 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 626 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 627 \\ d.platform, COALESCE(d.path, '') as path, 628 - \\ COALESCE(d.cover_image, '') as cover_image 629 \\FROM documents_fts f 630 \\JOIN documents d ON f.uri = d.uri 631 \\WHERE documents_fts MATCH ? AND (? = '' OR d.did = ?) 632 \\ORDER BY rank LIMIT 40 633 , .{ fts_query, author_val, author_val }); ··· 659 \\SELECT d.uri, d.did, d.title, '' as snippet, 660 \\ d.created_at, d.rkey, p.base_path, 661 \\ 1 as has_publication, d.platform, COALESCE(d.path, '') as path, 662 - \\ COALESCE(d.cover_image, '') as cover_image 663 \\FROM documents d 664 \\JOIN publications p ON d.publication_uri = p.uri 665 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 867 .platform = r.platform, 868 .path = r.path, 869 .coverImage = extras.cover_images.get(r.uri) orelse "", 870 }); 871 count += 1; 872 } ··· 1083 }; 1084 try jw.objectField("coverImage"); 1085 try jw.write(cover); 1086 try jw.objectField("createdAt"); 1087 try jw.write(jsonStr(obj, "createdAt")); 1088 try jw.objectField("source"); ··· 1193 .platform = r.platform, 1194 .path = r.path, 1195 .coverImage = extras.cover_images.get(r.uri) orelse "", 1196 }); 1197 } 1198 try jw.endArray(); ··· 1206 const LocalExtras = struct { 1207 snippets: std.StringHashMap([]const u8), 1208 cover_images: std.StringHashMap([]const u8), 1209 }; 1210 1211 - /// Fetch content previews and cover images from local SQLite for a list of URIs. 1212 /// Gracefully returns empty maps if local db is unavailable. 1213 fn fetchLocalExtras(alloc: Allocator, uris: []const []const u8) LocalExtras { 1214 var snippets = std.StringHashMap([]const u8).init(alloc); 1215 var cover_images = std.StringHashMap([]const u8).init(alloc); 1216 - const local = db.getLocalDb() orelse return .{ .snippets = snippets, .cover_images = cover_images }; 1217 for (uris) |uri| { 1218 var rows = local.query( 1219 - "SELECT substr(content, 1, 200), COALESCE(cover_image, '') FROM documents WHERE uri = ?", 1220 .{uri}, 1221 ) catch continue; 1222 defer rows.deinit(); ··· 1231 const duped = alloc.dupe(u8, cover) catch continue; 1232 cover_images.put(uri, duped) catch continue; 1233 } 1234 } 1235 } 1236 - return .{ .snippets = snippets, .cover_images = cover_images }; 1237 } 1238 1239 // --- JSON helpers (for hybrid search parsing) ---
··· 33 path: []const u8 = "", // URL path from record (e.g., "/001") 34 source: []const u8 = "", 35 coverImage: []const u8 = "", 36 + publicationName: []const u8 = "", 37 }; 38 39 /// Document search result (internal) ··· 49 platform: []const u8, 50 path: []const u8, 51 coverImage: []const u8, 52 + publicationName: []const u8, 53 54 fn fromRow(row: db.Row) Doc { 55 return .{ ··· 64 .platform = row.text(8), 65 .path = row.text(9), 66 .coverImage = row.text(10), 67 + .publicationName = row.text(11), 68 }; 69 } 70 ··· 81 .platform = row.text(8), 82 .path = row.text(9), 83 .coverImage = row.text(10), 84 + .publicationName = row.text(11), 85 }; 86 } 87 ··· 98 .platform = self.platform, 99 .path = self.path, 100 .coverImage = self.coverImage, 101 + .publicationName = self.publicationName, 102 }; 103 } 104 }; ··· 107 \\SELECT d.uri, d.did, d.title, '' as snippet, 108 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 109 \\ d.platform, COALESCE(d.path, '') as path, 110 + \\ COALESCE(d.cover_image, '') as cover_image, 111 + \\ COALESCE(p.name, '') as publication_name 112 \\FROM documents d 113 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 114 \\JOIN document_tags dt ON d.uri = dt.document_uri 115 \\WHERE dt.tag = :tag 116 \\ORDER BY d.created_at DESC LIMIT 40 ··· 121 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 122 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 123 \\ d.platform, COALESCE(d.path, '') as path, 124 + \\ COALESCE(d.cover_image, '') as cover_image, 125 + \\ COALESCE(p.name, '') as publication_name 126 \\FROM documents_fts f 127 \\JOIN documents d ON f.uri = d.uri 128 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 129 \\JOIN document_tags dt ON d.uri = dt.document_uri 130 \\WHERE documents_fts MATCH :query AND dt.tag = :tag 131 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 ··· 136 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 137 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 138 \\ d.platform, COALESCE(d.path, '') as path, 139 + \\ COALESCE(d.cover_image, '') as cover_image, 140 + \\ COALESCE(p.name, '') as publication_name 141 \\FROM documents_fts f 142 \\JOIN documents d ON f.uri = d.uri 143 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 144 \\WHERE documents_fts MATCH :query 145 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 146 ); ··· 150 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 151 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 152 \\ d.platform, COALESCE(d.path, '') as path, 153 + \\ COALESCE(d.cover_image, '') as cover_image, 154 + \\ COALESCE(p.name, '') as publication_name 155 \\FROM documents_fts f 156 \\JOIN documents d ON f.uri = d.uri 157 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 158 \\WHERE documents_fts MATCH :query AND d.created_at >= :since 159 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 160 ); ··· 164 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 165 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 166 \\ d.platform, COALESCE(d.path, '') as path, 167 + \\ COALESCE(d.cover_image, '') as cover_image, 168 + \\ COALESCE(p.name, '') as publication_name 169 \\FROM documents_fts f 170 \\JOIN documents d ON f.uri = d.uri 171 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 172 \\WHERE documents_fts MATCH :query AND d.platform = :platform 173 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 174 ); ··· 178 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 179 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 180 \\ d.platform, COALESCE(d.path, '') as path, 181 + \\ COALESCE(d.cover_image, '') as cover_image, 182 + \\ COALESCE(p.name, '') as publication_name 183 \\FROM documents_fts f 184 \\JOIN documents d ON f.uri = d.uri 185 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 186 \\WHERE documents_fts MATCH :query AND d.platform = :platform AND d.created_at >= :since 187 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 188 ); ··· 191 \\SELECT d.uri, d.did, d.title, '' as snippet, 192 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 193 \\ d.platform, COALESCE(d.path, '') as path, 194 + \\ COALESCE(d.cover_image, '') as cover_image, 195 + \\ COALESCE(p.name, '') as publication_name 196 \\FROM documents d 197 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 198 \\JOIN document_tags dt ON d.uri = dt.document_uri 199 \\WHERE dt.tag = :tag AND d.platform = :platform 200 \\ORDER BY d.created_at DESC LIMIT 40 ··· 205 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 206 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 207 \\ d.platform, COALESCE(d.path, '') as path, 208 + \\ COALESCE(d.cover_image, '') as cover_image, 209 + \\ COALESCE(p.name, '') as publication_name 210 \\FROM documents_fts f 211 \\JOIN documents d ON f.uri = d.uri 212 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 213 \\JOIN document_tags dt ON d.uri = dt.document_uri 214 \\WHERE documents_fts MATCH :query AND dt.tag = :tag AND d.platform = :platform 215 \\ORDER BY rank + (julianday('now') - julianday(d.created_at)) / 30.0 LIMIT 40 ··· 219 \\SELECT d.uri, d.did, d.title, '' as snippet, 220 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 221 \\ d.platform, COALESCE(d.path, '') as path, 222 + \\ COALESCE(d.cover_image, '') as cover_image, 223 + \\ COALESCE(p.name, '') as publication_name 224 \\FROM documents d 225 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 226 \\WHERE d.platform = :platform 227 \\ORDER BY d.created_at DESC LIMIT 40 228 ); ··· 231 \\SELECT d.uri, d.did, d.title, '' as snippet, 232 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 233 \\ d.platform, COALESCE(d.path, '') as path, 234 + \\ COALESCE(d.cover_image, '') as cover_image, 235 + \\ COALESCE(p.name, '') as publication_name 236 \\FROM documents d 237 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 238 \\WHERE d.did = :author 239 \\ORDER BY d.created_at DESC LIMIT 40 240 ); ··· 243 \\SELECT d.uri, d.did, d.title, '' as snippet, 244 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 245 \\ d.platform, COALESCE(d.path, '') as path, 246 + \\ COALESCE(d.cover_image, '') as cover_image, 247 + \\ COALESCE(p.name, '') as publication_name 248 \\FROM documents d 249 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 250 \\WHERE d.did = :author AND d.platform = :platform 251 \\ORDER BY d.created_at DESC LIMIT 40 252 ); ··· 260 \\ p.base_path, 261 \\ 1 as has_publication, 262 \\ d.platform, COALESCE(d.path, '') as path, 263 + \\ COALESCE(d.cover_image, '') as cover_image, 264 + \\ COALESCE(p.name, '') as publication_name 265 \\FROM documents d 266 \\JOIN publications p ON d.publication_uri = p.uri 267 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 275 \\ p.base_path, 276 \\ 1 as has_publication, 277 \\ d.platform, COALESCE(d.path, '') as path, 278 + \\ COALESCE(d.cover_image, '') as cover_image, 279 + \\ COALESCE(p.name, '') as publication_name 280 \\FROM documents d 281 \\JOIN publications p ON d.publication_uri = p.uri 282 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 290 \\ p.base_path, 291 \\ 1 as has_publication, 292 \\ d.platform, COALESCE(d.path, '') as path, 293 + \\ COALESCE(d.cover_image, '') as cover_image, 294 + \\ COALESCE(p.name, '') as publication_name 295 \\FROM documents d 296 \\JOIN publications p ON d.publication_uri = p.uri 297 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 305 \\ p.base_path, 306 \\ 1 as has_publication, 307 \\ d.platform, COALESCE(d.path, '') as path, 308 + \\ COALESCE(d.cover_image, '') as cover_image, 309 + \\ COALESCE(p.name, '') as publication_name 310 \\FROM documents d 311 \\JOIN publications p ON d.publication_uri = p.uri 312 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 601 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 602 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 603 \\ d.platform, COALESCE(d.path, '') as path, 604 + \\ COALESCE(d.cover_image, '') as cover_image, 605 + \\ COALESCE(p.name, '') as publication_name 606 \\FROM documents_fts f 607 \\JOIN documents d ON f.uri = d.uri 608 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 609 \\WHERE documents_fts MATCH ? AND d.platform = ? AND (? = '' OR d.did = ?) 610 \\ORDER BY rank LIMIT 40 611 , .{ fts_query, platform, author_val, author_val }); ··· 630 \\SELECT d.uri, d.did, d.title, '' as snippet, 631 \\ d.created_at, d.rkey, p.base_path, 632 \\ 1 as has_publication, d.platform, COALESCE(d.path, '') as path, 633 + \\ COALESCE(d.cover_image, '') as cover_image, 634 + \\ COALESCE(p.name, '') as publication_name 635 \\FROM documents d 636 \\JOIN publications p ON d.publication_uri = p.uri 637 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 659 \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 660 \\ d.created_at, d.rkey, d.base_path, d.has_publication, 661 \\ d.platform, COALESCE(d.path, '') as path, 662 + \\ COALESCE(d.cover_image, '') as cover_image, 663 + \\ COALESCE(p.name, '') as publication_name 664 \\FROM documents_fts f 665 \\JOIN documents d ON f.uri = d.uri 666 + \\LEFT JOIN publications p ON d.publication_uri = p.uri 667 \\WHERE documents_fts MATCH ? AND (? = '' OR d.did = ?) 668 \\ORDER BY rank LIMIT 40 669 , .{ fts_query, author_val, author_val }); ··· 695 \\SELECT d.uri, d.did, d.title, '' as snippet, 696 \\ d.created_at, d.rkey, p.base_path, 697 \\ 1 as has_publication, d.platform, COALESCE(d.path, '') as path, 698 + \\ COALESCE(d.cover_image, '') as cover_image, 699 + \\ COALESCE(p.name, '') as publication_name 700 \\FROM documents d 701 \\JOIN publications p ON d.publication_uri = p.uri 702 \\JOIN publications_fts pf ON p.uri = pf.uri ··· 904 .platform = r.platform, 905 .path = r.path, 906 .coverImage = extras.cover_images.get(r.uri) orelse "", 907 + .publicationName = extras.pub_names.get(r.uri) orelse "", 908 }); 909 count += 1; 910 } ··· 1121 }; 1122 try jw.objectField("coverImage"); 1123 try jw.write(cover); 1124 + // for semantic-only results, pub name may need local DB fallback 1125 + const pub_name = blk: { 1126 + const existing = jsonStr(obj, "publicationName"); 1127 + if (existing.len > 0) break :blk existing; 1128 + if (bits & 0b10 != 0) break :blk hybrid_extras.pub_names.get(entry.uri) orelse ""; 1129 + break :blk existing; 1130 + }; 1131 + try jw.objectField("publicationName"); 1132 + try jw.write(pub_name); 1133 try jw.objectField("createdAt"); 1134 try jw.write(jsonStr(obj, "createdAt")); 1135 try jw.objectField("source"); ··· 1240 .platform = r.platform, 1241 .path = r.path, 1242 .coverImage = extras.cover_images.get(r.uri) orelse "", 1243 + .publicationName = extras.pub_names.get(r.uri) orelse "", 1244 }); 1245 } 1246 try jw.endArray(); ··· 1254 const LocalExtras = struct { 1255 snippets: std.StringHashMap([]const u8), 1256 cover_images: std.StringHashMap([]const u8), 1257 + pub_names: std.StringHashMap([]const u8), 1258 }; 1259 1260 + /// Fetch content previews, cover images, and publication names from local SQLite for a list of URIs. 1261 /// Gracefully returns empty maps if local db is unavailable. 1262 fn fetchLocalExtras(alloc: Allocator, uris: []const []const u8) LocalExtras { 1263 var snippets = std.StringHashMap([]const u8).init(alloc); 1264 var cover_images = std.StringHashMap([]const u8).init(alloc); 1265 + var pub_names = std.StringHashMap([]const u8).init(alloc); 1266 + const local = db.getLocalDb() orelse return .{ .snippets = snippets, .cover_images = cover_images, .pub_names = pub_names }; 1267 for (uris) |uri| { 1268 var rows = local.query( 1269 + "SELECT substr(content, 1, 200), COALESCE(cover_image, ''), COALESCE((SELECT name FROM publications WHERE uri = documents.publication_uri), '') FROM documents WHERE uri = ?", 1270 .{uri}, 1271 ) catch continue; 1272 defer rows.deinit(); ··· 1281 const duped = alloc.dupe(u8, cover) catch continue; 1282 cover_images.put(uri, duped) catch continue; 1283 } 1284 + const pub_name = row.text(2); 1285 + if (pub_name.len > 0) { 1286 + const duped = alloc.dupe(u8, pub_name) catch continue; 1287 + pub_names.put(uri, duped) catch continue; 1288 + } 1289 } 1290 } 1291 + return .{ .snippets = snippets, .cover_images = cover_images, .pub_names = pub_names }; 1292 } 1293 1294 // --- JSON helpers (for hybrid search parsing) ---
+1
mcp/src/pub_search/_types.py
··· 20 path: str = "" 21 source: str = "" 22 score: float = 0.0 23 24 @computed_field 25 @property
··· 20 path: str = "" 21 source: str = "" 22 score: float = 0.0 23 + publicationName: str = "" 24 25 @computed_field 26 @property