Rust AppView - highly experimental!
1
fork

Configure Feed

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

Merge remote-tracking branch 'mia/main' into experiments

+79 -51
+54 -13
parakeet-db/src/models.rs
··· 138 138 139 139 pub content: String, 140 140 pub facets: Option<serde_json::Value>, 141 - pub languages: Vec<Option<String>>, 142 - pub tags: Vec<Option<String>>, 141 + pub languages: not_null_vec::TextArray, 142 + pub tags: not_null_vec::TextArray, 143 143 144 144 pub parent_uri: Option<String>, 145 145 pub parent_cid: Option<String>, ··· 149 149 pub embed: Option<String>, 150 150 pub embed_subtype: Option<String>, 151 151 152 - pub mentions: Option<Vec<Option<String>>>, 152 + pub mentions: Option<not_null_vec::TextArray>, 153 153 pub violates_threadgate: bool, 154 154 155 155 pub created_at: DateTime<Utc>, ··· 237 237 pub cid: String, 238 238 pub post_uri: String, 239 239 240 - pub detached: Vec<Option<String>>, 241 - pub rules: Vec<Option<String>>, 240 + pub detached: not_null_vec::TextArray, 241 + pub rules: not_null_vec::TextArray, 242 242 243 243 pub created_at: DateTime<Utc>, 244 244 pub indexed_at: NaiveDateTime, ··· 253 253 pub cid: String, 254 254 pub post_uri: String, 255 255 256 - pub hidden_replies: Vec<Option<String>>, 257 - pub allow: Option<Vec<Option<String>>>, 258 - pub allowed_lists: Option<Vec<Option<String>>>, 256 + pub hidden_replies: not_null_vec::TextArray, 257 + pub allow: Option<not_null_vec::TextArray>, 258 + pub allowed_lists: Option<not_null_vec::TextArray>, 259 259 260 260 pub record: serde_json::Value, 261 261 ··· 277 277 pub description: Option<String>, 278 278 pub description_facets: Option<serde_json::Value>, 279 279 pub list: String, 280 - pub feeds: Option<Vec<Option<String>>>, 280 + pub feeds: Option<not_null_vec::TextArray>, 281 281 282 282 pub created_at: DateTime<Utc>, 283 283 pub indexed_at: NaiveDateTime, ··· 291 291 pub did: String, 292 292 pub cid: String, 293 293 294 - pub reasons: Option<Vec<Option<String>>>, 295 - pub subject_types: Option<Vec<Option<String>>>, 296 - pub subject_collections: Option<Vec<Option<String>>>, 294 + pub reasons: Option<not_null_vec::TextArray>, 295 + pub subject_types: Option<not_null_vec::TextArray>, 296 + pub subject_collections: Option<not_null_vec::TextArray>, 297 297 298 298 pub created_at: NaiveDateTime, 299 299 pub indexed_at: NaiveDateTime, ··· 403 403 pub subject: String, 404 404 pub subject_cid: Option<String>, 405 405 pub subject_type: String, 406 - pub tags: Vec<Option<String>>, 406 + pub tags: not_null_vec::TextArray, 407 407 pub created_at: DateTime<Utc>, 408 408 } 409 409 ··· 431 431 pub typ: String, 432 432 pub sort_at: DateTime<Utc>, 433 433 } 434 + 435 + mod not_null_vec { 436 + use diesel::deserialize::FromSql; 437 + use diesel::pg::Pg; 438 + use diesel::sql_types::{Array, Nullable, Text}; 439 + use diesel::{deserialize, FromSqlRow}; 440 + use serde::{Deserialize, Serialize}; 441 + use std::ops::{Deref, DerefMut}; 442 + 443 + #[derive(Clone, Debug, Default, Serialize, Deserialize, FromSqlRow)] 444 + #[diesel(sql_type = Array<Nullable<Text>>)] 445 + pub struct TextArray(pub Vec<String>); 446 + 447 + impl FromSql<Array<Nullable<Text>>, Pg> for TextArray { 448 + fn from_sql(bytes: diesel::pg::PgValue<'_>) -> deserialize::Result<Self> { 449 + let vec_with_nulls = 450 + <Vec<Option<String>> as FromSql<Array<Nullable<Text>>, Pg>>::from_sql(bytes)?; 451 + Ok(TextArray(vec_with_nulls.into_iter().flatten().collect())) 452 + } 453 + } 454 + 455 + impl Deref for TextArray { 456 + type Target = Vec<String>; 457 + 458 + fn deref(&self) -> &Self::Target { 459 + &self.0 460 + } 461 + } 462 + 463 + impl DerefMut for TextArray { 464 + fn deref_mut(&mut self) -> &mut Self::Target { 465 + &mut self.0 466 + } 467 + } 468 + 469 + impl From<TextArray> for Vec<String> { 470 + fn from(v: TextArray) -> Vec<String> { 471 + v.0 472 + } 473 + } 474 + }
+5 -9
parakeet/src/hydration/labeler.rs
··· 42 42 likes: Option<i32>, 43 43 ) -> LabelerViewDetailed { 44 44 let reason_types = labeler.reasons.map(|v| { 45 - v.into_iter() 46 - .flatten() 47 - .filter_map(|v| ReasonType::from_str(&v).ok()) 45 + v.iter() 46 + .filter_map(|v| ReasonType::from_str(v).ok()) 48 47 .collect() 49 48 }); 50 49 ··· 74 73 }) 75 74 .collect(); 76 75 let subject_types = labeler.subject_types.map(|v| { 77 - v.into_iter() 78 - .flatten() 79 - .filter_map(|v| SubjectType::from_str(&v).ok()) 76 + v.iter() 77 + .filter_map(|v| SubjectType::from_str(v).ok()) 80 78 .collect() 81 79 }); 82 - let subject_collections = labeler 83 - .subject_collections 84 - .map(|v| v.into_iter().flatten().collect()); 80 + let subject_collections = labeler.subject_collections.map(Vec::from); 85 81 86 82 LabelerViewDetailed { 87 83 uri: format!("at://{}/app.bsky.labeler.service/self", labeler.did),
+12 -20
parakeet/src/hydration/posts/mod.rs
··· 20 20 ) -> Option<ThreadgateView> { 21 21 let threadgate = threadgate?; 22 22 23 - let lists = threadgate 24 - .allowed_lists 25 - .as_ref() 26 - .map_or_else(Vec::new, |allowed_lists| { 27 - allowed_lists.iter().flatten().cloned().collect() 28 - }); 23 + let lists = match threadgate.allowed_lists.as_ref() { 24 + Some(allowed_lists) => allowed_lists.clone().into(), 25 + None => Vec::new(), 26 + }; 29 27 let lists = self.hydrate_lists_basic(lists).await; 30 28 31 29 Some(build_threadgate_view( ··· 40 38 ) -> HashMap<String, ThreadgateView> { 41 39 let lists = threadgates.iter().fold(Vec::new(), |mut acc, c| { 42 40 if let Some(lists) = &c.allowed_lists { 43 - acc.extend(lists.iter().flatten().cloned()); 41 + acc.extend(lists.clone().0); 44 42 } 45 43 acc 46 44 }); ··· 49 47 threadgates 50 48 .into_iter() 51 49 .map(|threadgate| { 52 - let this_lists = 53 - threadgate 54 - .allowed_lists 55 - .as_ref() 56 - .map_or_else(Vec::new, |allowed_lists| { 57 - allowed_lists 58 - .iter() 59 - .filter_map(|v| { 60 - let v = v.clone()?; 61 - lists.get(&v).cloned() 62 - }) 63 - .collect() 64 - }); 50 + let this_lists = match &threadgate.allowed_lists { 51 + Some(allowed_lists) => allowed_lists 52 + .iter() 53 + .filter_map(|v| lists.get(v).cloned()) 54 + .collect(), 55 + None => Vec::new(), 56 + }; 65 57 66 58 ( 67 59 threadgate.at_uri.clone(),
+3 -7
parakeet/src/hydration/starter_packs.rs
··· 96 96 let feeds = sp 97 97 .feeds 98 98 .clone() 99 - .unwrap_or_default() 100 - .into_iter() 101 - .flatten() 102 - .collect(); 103 - let feeds = self.hydrate_feedgens(feeds).await.into_values().collect(); 99 + .unwrap_or_default(); 100 + let feeds = self.hydrate_feedgens(feeds.into()).await.into_values().collect(); 104 101 105 102 Some(build_spview(sp, creator, labels, list, feeds)) 106 103 } ··· 119 116 let feeds = packs 120 117 .values() 121 118 .filter_map(|pack| pack.feeds.clone()) 122 - .flat_map(|feeds| feeds.into_iter().flatten()) 119 + .flat_map(Vec::from) 123 120 .collect(); 124 121 125 122 let creators = self.hydrate_profiles_basic(creators).await; ··· 133 130 let list = lists.get(&pack.list).cloned(); 134 131 let feeds = pack.feeds.as_ref().map(|v| { 135 132 v.iter() 136 - .flatten() 137 133 .filter_map(|feed| feeds.get(feed).cloned()) 138 134 .collect() 139 135 });
+4 -1
parakeet/src/loaders/post.rs
··· 1 1 use crate::db; 2 2 use dataloader::BatchFn; 3 + use diesel::dsl::sql; 3 4 use diesel::prelude::*; 4 5 use diesel_async::pooled_connection::deadpool::Pool; 5 6 use diesel_async::AsyncPgConnection; ··· 14 15 15 16 let res = diesel_async::RunQueryDsl::load( 16 17 schema::posts::table 17 - .left_join(schema::threadgates::table) 18 + .left_join(schema::threadgates::table.on( 19 + schema::threadgates::post_uri.eq(sql("coalesce(posts.root_uri, posts.at_uri)")), 20 + )) 18 21 .select(( 19 22 models::Post::as_select(), 20 23 Option::<models::Threadgate>::as_select(),
+1 -1
parakeet/src/xrpc/community_lexicon/bookmarks.rs
··· 61 61 .into_iter() 62 62 .map(|bookmark| Bookmark { 63 63 subject: bookmark.subject, 64 - tags: bookmark.tags.into_iter().flatten().collect(), 64 + tags: bookmark.tags.into(), 65 65 created_at: bookmark.created_at, 66 66 }) 67 67 .collect();