···148148 }
149149}
150150151151-// NOTE: get_post_id_by_uri removed - posts table no longer has id column
152152-// Use natural keys (actor_id, rkey) directly instead
153153-154151/// Batch lookup post IDs from AT URIs
155152///
156153/// Returns HashMap mapping AT URI to internal post ID
···183180 return Ok(HashMap::new());
184181 }
185182186186- // TODO: Update cache to use PostKey instead of i64
187187- // For now, skip cache and query all URIs
183183+ // Cache currently uses i64 IDs, querying directly for now
188184 let mut result = HashMap::new();
189185 let uris_to_query = uris.to_vec();
190186···356352 Some((uri, post_key))
357353 })
358354 .collect();
359359-360360- // TODO: Populate cache with new results (cache needs to be updated to use PostKey)
361361- // if let Some(cache) = cache {
362362- // cache.set_post_ids(&db_results).await;
363363- // }
364355365356 // Return database results
366357 result.extend(db_results);
+3-5
parakeet/src/db/search.rs
···205205 tags: &[String],
206206 since: Option<chrono::NaiveDateTime>,
207207 until: Option<chrono::NaiveDateTime>,
208208- _sort: &str, // TODO: Implement BM25 ranking for "top" sort
208208+ _sort: &str, // TODO: BM25 ranking for "top" sort
209209 limit: i64,
210210 cursor: Option<f64>,
211211) -> QueryResult<Vec<PostSearchResult>> {
···235235 micros << 10
236236 });
237237238238- // For token-based search, "top" sort doesn't have a good relevance metric yet
239239- // So we'll just use chronological order (latest) for both modes
240240- // TODO: Implement BM25 or similar for relevance ranking
238238+ // Using chronological order for now - "top" sort requires relevance ranking
241239242240 if has_text {
243241 // Search with text tokens
···359357 tags: &[String],
360358 since: Option<chrono::NaiveDateTime>,
361359 until: Option<chrono::NaiveDateTime>,
362362- _sort: &str, // TODO: Implement BM25 ranking for "top" sort
360360+ _sort: &str, // TODO: BM25 ranking for "top" sort
363361 limit: i64,
364362 cursor: Option<f64>,
365363) -> QueryResult<Vec<PostSearchResultByIds>> {
···3838 // Return None if profile doesn't exist
3939 let profile = profile.as_ref()?;
40404141- // Why says there's a way for the client to configure which verifiers to trust (explicitly
4242- // mentioning the deer.social client which doesn't run an AppView) - except I can't see any
4343- // parameters or headers in requests, or anywhere in social-app where one would configure that.
4444- // Presuming this means that this will be an option eventually (or I'm missing something)
4141+ // Use configured trusted verifiers.
4542 let accept_verifiers = TRUSTED_VERIFIERS.get().unwrap();
4643 let is_trusted_verifier = accept_verifiers.iter().any(|v| v == did);
4744
+1-1
parakeet/src/hydration/starter_packs.rs
···4040 record: enriched.record,
4141 creator,
4242 list,
4343- list_items_sample: vec![], // TODO: we should do this, but the app seems to be okay without?
4343+ list_items_sample: vec![],
4444 feeds,
4545 list_item_count,
4646 joined_week_count: 0,
+1-2
parakeet/src/loaders/embed.rs
···11use serde::{Deserialize, Serialize};
2233-// EmbedLoader removed - now using hydrate_embed_from_post() instead
44-// This file now only contains type definitions used by hydration code
33+// Type definitions used by embed hydration code
5465// Enriched PostEmbedRecord with reconstructed URI
76#[derive(Debug, Clone, Serialize, Deserialize)]
+1-1
parakeet/src/loaders/mod.rs
···1212use diesel_async::pooled_connection::deadpool::Pool;
1313use diesel_async::AsyncPgConnection;
14141515-// Re-export public types (EmbedLoader removed - now using hydrate_embed_from_post() instead)
1515+// Re-export public types
1616pub use embed::{EmbedLoaderRet, EnrichedPostEmbedRecord, PostEmbedImage, PostEmbedVideo, PostEmbedExt};
1717pub use feed::{EnrichedFeedGen, FeedGenKey, FeedGenLoader, LikeRecordLoader};
1818pub use labeler::{EnrichedLabeler, LabelLoader, LabelServiceLoader, LabelServiceLoaderRet};
+5-12
parakeet/src/loaders/post.rs
···44use parakeet_db::models::{self, array_helpers};
55use std::collections::HashMap;
6677-/// Extended Post model with computed fields from the old schema
88-/// (parent_uri, root_uri, did, cid, at_uri reconstructed from FKs)
77+/// Post model with computed fields (URIs reconstructed from natural keys)
98#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
109pub struct HydratedPost {
1110 pub post: models::Post,
···176175/// Tests can call this to validate SQL syntax without duplicating the query.
177176/// Build SQL query for batch loading posts by natural keys (actor_id, rkey)
178177///
179179-/// OPTIMIZED: No tid_timestamp() function call, no actors JOIN
180180-/// - Timestamp computed in Rust via tid_to_datetime(rkey)
181181-/// - Natural keys (actor_id, rkey) passed directly from parsed URIs
182182-/// - DIDs already available from parsing, no JOIN needed
183183-/// - Uses ANY array for 26x better index usage vs IN subquery (0.7ms vs 5.6ms for 30 posts)
178178+/// Uses natural keys directly avoiding actor table joins.
179179+/// Timestamps computed in Rust via tid_to_datetime(rkey).
184180///
185181/// This function is public for testing purposes.
186182pub fn build_posts_batch_query() -> &'static str {
···969965 .collect();
970966 let post_processing_time = post_processing_start.elapsed().as_secs_f64() * 1000.0;
971967972972- // OPTIMIZED: Facets are now loaded inline with posts (no separate query needed)
973968 // Collect all mention actor IDs from facets to batch lookup DIDs
974969 let facets_start = std::time::Instant::now();
975970 let mut mention_actor_ids: Vec<i32> = Vec::new();
···10301025 }
10311026 use parakeet_db::models::array_helpers::ThreadgateRuleArray;
1032102710331033- // Generate synthetic CID for threadgate (deterministic based on post natural key)
10341034- // Since we no longer store the real threadgate CID, we create a consistent placeholder
10281028+ // Generate deterministic CID for threadgate based on post natural key
10351029 let synthetic_cid = parakeet_db::cid_util::post_cid_string(post.actor_id, post.rkey);
1036103010371037- // TODO: Load allowed_lists from threadgate_allowed_lists junction table if needed
10381031 let allowed_lists = None;
1039103210401033 // Store hidden replies as (actor_id, rkey) pairs - URI construction deferred to hydration
···11541147 _threadgate_hidden_rkeys,
11551148 labels,
11561149 )| {
11571157- // OPTIMIZED: Process facets from inline composite fields (no separate query)
11501150+ // Process facets from inline composite fields
11581151 let facet_vec = vec![facet_1, facet_2, facet_3, facet_4, facet_5, facet_6, facet_7, facet_8];
11591152 let facets: Vec<FacetData> = facet_vec
11601153 .into_iter()
+2-3
parakeet/src/timeline_cache.rs
···109109 let prefix = format!("timeline:{}:", actor_id);
110110111111 // Invalidate all entries matching the prefix
112112- // Note: We can't count deletions with moka's Fn closure API
112112+ // Deletion metrics not tracked in current implementation
113113 drop(self.cache.invalidate_entries_if(move |key, _| {
114114 key.starts_with(&prefix)
115115 }));
···219219 let prefix = format!("authorfeed:{}:", actor_id);
220220221221 // Invalidate all entries matching the prefix
222222- // Note: We can't count deletions with moka's Fn closure API
222222+ // Deletion metrics not tracked in current implementation
223223 drop(self.cache.invalidate_entries_if(move |key, _| {
224224 key.starts_with(&prefix)
225225 }));
···336336337337 // Invalidate
338338 let _deleted = cache.invalidate_by_actor_id(actor_id).await;
339339- // Note: moka's API doesn't allow counting deletions
340339341340 // Verify cache cleared
342341 assert!(cache.get(actor_id, None).await.is_none());
+1-2
parakeet/src/xrpc/app_bsky/feed/feedgen.rs
···197197 .and_then(|c| c.parse::<usize>().ok())
198198 .unwrap_or(0);
199199200200- // Fetch and rank feeds
201201- // TODO: Consider adding moka cache if this becomes a bottleneck
200200+ // TODO: Cache opportunity - feed rankings
202201 let mut conn = state.pool.get().await?;
203202204203 // Fetch all feedgens ordered by like count (uses idx_feedgens_like_count_desc index)
-3
parakeet/src/xrpc/app_bsky/feed/posts/helpers.rs
···1414#[expect(dead_code)]
1515const FEEDGEN_SERVICE_ID: &str = "#bsky_fg";
16161717-// Note: embed_type filtering is now done in SQL string interpolation in feed queries
1818-// The old diesel-based embed_type_filter function has been removed
1919-2017pub(super) async fn get_feed_skeleton(
2118 client: &reqwest::Client,
2219 feed: &str,
+1-2
parakeet/src/xrpc/app_bsky/graph/suggestions.rs
···4242 &actor_did,
4343 ).await?;
44444545- // Compute suggestions
4646- // TODO: Consider adding moka cache if this becomes a bottleneck
4545+ // TODO: Cache opportunity - similarity suggestions
47464847 // Get input actor's follower count for similarity ranking
4948 let input_stats = state
+4-7
parakeet/src/xrpc/app_bsky/unspecced/mod.rs
···155155) -> XrpcResult<Json<GetSuggestedFeedsResponse>> {
156156 let limit = query.limit.unwrap_or(50).clamp(1, 100) as usize;
157157158158- // Fetch and rank feeds (no caching - recalculated each time)
159159- // TODO: Consider adding moka cache if this becomes a bottleneck
158158+ // TODO: Cache opportunity - feed rankings
160159 let mut conn = state.pool.get().await?;
161160162161 // Fetch all feedgens ordered by like count (uses idx_feedgens_like_count_desc index)
···234233 let limit = query.limit.unwrap_or(25).clamp(1, 100) as usize;
235234 // Category parameter is accepted but ignored for now
236235237237- // Compute global suggestions (no caching - recalculated each time)
238238- // TODO: Consider adding moka cache if this becomes a bottleneck
236236+ // TODO: Cache opportunity - actor suggestions
239237 let mut conn = state.pool.get().await?;
240238241239 // Get top 1000 most-followed DIDs by counting follows in our database
···333331 .and_then(|c| c.parse::<usize>().ok())
334332 .unwrap_or(0);
335333336336- // Fetch and rank feeds (no caching - recalculated each time)
337337- // TODO: Consider adding moka cache if this becomes a bottleneck
334334+ // TODO: Cache opportunity - feed rankings
338335 let mut conn = state.pool.get().await?;
339336340337 // Fetch all feedgens ordered by like count (uses idx_feedgens_like_count_desc index)
···462459 let limit = query.limit.unwrap_or(25).clamp(1, 100) as usize;
463460464461 // Compute rankings (no caching - recalculated each time)
465465- // TODO: Consider adding moka cache if this becomes a bottleneck
462462+ // TODO: Cache opportunity - starter pack rankings
466463 let mut conn = state.pool.get().await?;
467464468465 // Get all starter packs with their owners