Parakeet is a Rust-based Bluesky AppServer aiming to implement most of the functionality required to support the Bluesky client
appview atproto bluesky rust appserver
69
fork

Configure Feed

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

fix: datetimes

mia.omg.lol a3926001 13893e3f

verified
+82 -51
+1
Cargo.lock
··· 2560 2560 dependencies = [ 2561 2561 "chrono", 2562 2562 "cid", 2563 + "jacquard-common", 2563 2564 "serde", 2564 2565 "serde_json", 2565 2566 ]
+1 -1
crates/consumer/src/db/record.rs
··· 44 44 &rec.subject, 45 45 &rec_type, 46 46 &rec.tags, 47 - &rec.created_at, 47 + &rec.created_at.as_ref().to_utc(), 48 48 ], 49 49 ) 50 50 .await
+1
crates/lexica/Cargo.toml
··· 6 6 [dependencies] 7 7 chrono = { version = "0.4", features = ["serde"] } 8 8 cid = { version = "0.11", features = ["serde"] } 9 + jacquard-common = { version = "0.9.5", default-features = false } 9 10 serde = { version = "1.0", features = ["derive"] } 10 11 serde_json = "1.0"
+8 -8
crates/lexica/src/app_bsky/actor.rs
··· 1 1 use crate::app_bsky::embed::External; 2 2 use crate::app_bsky::graph::ListViewBasic; 3 3 use crate::com_atproto::label::Label; 4 - use chrono::prelude::*; 4 + use jacquard_common::types::string::Datetime; 5 5 use serde::{Deserialize, Serialize}; 6 6 use std::fmt::Display; 7 7 use std::str::FromStr; ··· 163 163 #[serde(skip_serializing_if = "Option::is_none")] 164 164 pub pronouns: Option<String>, 165 165 166 - pub created_at: DateTime<Utc>, 166 + pub created_at: Datetime, 167 167 } 168 168 169 169 #[derive(Clone, Debug, Serialize)] ··· 191 191 #[serde(skip_serializing_if = "Option::is_none")] 192 192 pub pronouns: Option<String>, 193 193 194 - pub created_at: DateTime<Utc>, 195 - pub indexed_at: NaiveDateTime, 194 + pub created_at: Datetime, 195 + pub indexed_at: Datetime, 196 196 } 197 197 198 198 #[derive(Debug, Serialize)] ··· 230 230 #[serde(skip_serializing_if = "Option::is_none")] 231 231 pub website: Option<String>, 232 232 233 - pub created_at: DateTime<Utc>, 234 - pub indexed_at: NaiveDateTime, 233 + pub created_at: Datetime, 234 + pub indexed_at: Datetime, 235 235 } 236 236 237 237 #[derive(Clone, Debug, Serialize)] ··· 240 240 pub issuer: String, 241 241 pub uri: String, 242 242 pub is_valid: bool, 243 - pub created_at: DateTime<Utc>, 243 + pub created_at: Datetime, 244 244 } 245 245 246 246 #[derive(Clone, Debug, Serialize)] ··· 275 275 #[serde(skip_serializing_if = "Option::is_none")] 276 276 pub embed: Option<StatusViewEmbed>, 277 277 #[serde(skip_serializing_if = "Option::is_none")] 278 - pub expires_at: Option<DateTime<Utc>>, 278 + pub expires_at: Option<Datetime>, 279 279 #[serde(skip_serializing_if = "Option::is_none")] 280 280 pub is_active: Option<bool>, 281 281 }
+2 -2
crates/lexica/src/app_bsky/bookmark.rs
··· 1 1 use crate::app_bsky::feed::{BlockedAuthor, PostView}; 2 2 use crate::StrongRef; 3 - use chrono::prelude::*; 3 + use jacquard_common::types::string::Datetime; 4 4 use serde::Serialize; 5 5 6 6 #[derive(Clone, Debug, Serialize)] ··· 8 8 pub struct BookmarkView { 9 9 pub subject: StrongRef, 10 10 pub item: BookmarkViewItem, 11 - pub created_at: DateTime<Utc>, 11 + pub created_at: Datetime, 12 12 } 13 13 14 14 #[derive(Clone, Debug, Serialize)]
+2 -2
crates/lexica/src/app_bsky/embed.rs
··· 4 4 use crate::app_bsky::labeler::LabelerView; 5 5 use crate::app_bsky::RecordStats; 6 6 use crate::com_atproto::label::Label; 7 - use chrono::prelude::*; 7 + use jacquard_common::types::string::Datetime; 8 8 use serde::{Deserialize, Serialize}; 9 9 10 10 #[derive(Clone, Debug, Deserialize, Serialize)] ··· 106 106 pub stats: RecordStats, 107 107 #[serde(skip_serializing_if = "Option::is_none")] 108 108 pub embeds: Option<Vec<Embed>>, 109 - pub indexed_at: DateTime<Utc>, 109 + pub indexed_at: Datetime, 110 110 } 111 111 112 112 #[derive(Clone, Debug, Serialize)]
+6 -6
crates/lexica/src/app_bsky/feed.rs
··· 4 4 use crate::app_bsky::graph::ListViewBasic; 5 5 use crate::app_bsky::richtext::FacetMain; 6 6 use crate::com_atproto::label::Label; 7 - use chrono::prelude::*; 7 + use jacquard_common::types::string::Datetime; 8 8 use serde::{Deserialize, Serialize}; 9 9 use std::str::FromStr; 10 10 ··· 42 42 #[serde(skip_serializing_if = "Option::is_none")] 43 43 pub threadgate: Option<ThreadgateView>, 44 44 45 - pub indexed_at: DateTime<Utc>, 45 + pub indexed_at: Datetime, 46 46 } 47 47 48 48 #[derive(Debug, Serialize)] ··· 102 102 pub uri: Option<String>, 103 103 #[serde(skip_serializing_if = "Option::is_none")] 104 104 pub cid: Option<String>, 105 - pub indexed_at: DateTime<Utc>, 105 + pub indexed_at: Datetime, 106 106 } 107 107 108 108 #[derive(Debug, Serialize)] ··· 174 174 #[serde(skip_serializing_if = "Option::is_none")] 175 175 pub content_mode: Option<GeneratorContentMode>, 176 176 177 - pub indexed_at: DateTime<Utc>, 177 + pub indexed_at: Datetime, 178 178 } 179 179 180 180 #[derive(Copy, Clone, Debug, Serialize)] ··· 210 210 #[serde(rename_all = "camelCase")] 211 211 pub struct Like { 212 212 pub actor: ProfileView, 213 - pub created_at: DateTime<Utc>, 214 - pub indexed_at: DateTime<Utc>, 213 + pub created_at: Datetime, 214 + pub indexed_at: Datetime, 215 215 } 216 216 217 217 #[derive(Clone, Debug, Deserialize, Serialize)]
+5 -5
crates/lexica/src/app_bsky/graph.rs
··· 2 2 use crate::app_bsky::feed::GeneratorView; 3 3 use crate::app_bsky::richtext::FacetMain; 4 4 use crate::com_atproto::label::Label; 5 - use chrono::prelude::*; 5 + use jacquard_common::types::string::Datetime; 6 6 use serde::{Deserialize, Serialize}; 7 7 use std::str::FromStr; 8 8 ··· 31 31 #[serde(skip_serializing_if = "Vec::is_empty")] 32 32 pub labels: Vec<Label>, 33 33 34 - pub indexed_at: DateTime<Utc>, 34 + pub indexed_at: Datetime, 35 35 } 36 36 37 37 #[derive(Clone, Debug, Serialize)] ··· 57 57 #[serde(skip_serializing_if = "Vec::is_empty")] 58 58 pub labels: Vec<Label>, 59 59 60 - pub indexed_at: DateTime<Utc>, 60 + pub indexed_at: Datetime, 61 61 } 62 62 63 63 #[derive(Clone, Debug, Serialize)] ··· 113 113 114 114 #[serde(skip_serializing_if = "Vec::is_empty")] 115 115 pub labels: Vec<Label>, 116 - pub indexed_at: DateTime<Utc>, 116 + pub indexed_at: Datetime, 117 117 } 118 118 119 119 #[derive(Clone, Debug, Serialize)] ··· 130 130 131 131 #[serde(skip_serializing_if = "Vec::is_empty")] 132 132 pub labels: Vec<Label>, 133 - pub indexed_at: DateTime<Utc>, 133 + pub indexed_at: Datetime, 134 134 }
+3 -3
crates/lexica/src/app_bsky/labeler.rs
··· 1 1 use crate::app_bsky::actor::ProfileView; 2 2 use crate::com_atproto::label::{Label, LabelValueDefinition}; 3 3 use crate::com_atproto::moderation::{ReasonType, SubjectType}; 4 - use chrono::prelude::*; 4 + use jacquard_common::types::string::Datetime; 5 5 use serde::{Deserialize, Serialize}; 6 6 7 7 #[derive(Clone, Default, Debug, Serialize)] ··· 23 23 pub viewer: Option<LabelerViewerState>, 24 24 #[serde(skip_serializing_if = "Vec::is_empty")] 25 25 pub labels: Vec<Label>, 26 - pub indexed_at: DateTime<Utc>, 26 + pub indexed_at: Datetime, 27 27 } 28 28 29 29 #[derive(Clone, Debug, Serialize)] ··· 46 46 #[serde(skip_serializing_if = "Option::is_none")] 47 47 pub subject_collections: Option<Vec<String>>, 48 48 49 - pub indexed_at: DateTime<Utc>, 49 + pub indexed_at: Datetime, 50 50 } 51 51 52 52 #[derive(Clone, Debug, Deserialize, Serialize)]
+2 -2
crates/lexica/src/community_lexicon/bookmarks.rs
··· 1 - use chrono::prelude::*; 1 + use jacquard_common::types::string::Datetime; 2 2 use serde::{Deserialize, Serialize}; 3 3 4 4 #[derive(Clone, Debug, Deserialize, Serialize)] ··· 10 10 #[serde(default)] 11 11 #[serde(skip_serializing_if = "Vec::is_empty")] 12 12 pub tags: Vec<String>, 13 - pub created_at: DateTime<Utc>, 13 + pub created_at: Datetime, 14 14 }
+2 -1
crates/parakeet/src/hydration/feedgen.rs
··· 1 1 use crate::hydration::map_labels; 2 + use crate::utils::DateTimeExt; 2 3 use crate::xrpc::cdn::BskyCdn; 3 4 use lexica::app_bsky::actor::ProfileView; 4 5 use lexica::app_bsky::feed::{GeneratorContentMode, GeneratorView, GeneratorViewerState}; ··· 45 46 labels: map_labels(labels), 46 47 viewer, 47 48 content_mode, 48 - indexed_at: feedgen.created_at, 49 + indexed_at: feedgen.created_at.into_jacquard(), 49 50 } 50 51 } 51 52
+3 -2
crates/parakeet/src/hydration/labeler.rs
··· 1 1 use crate::hydration::{map_labels, StatefulHydrator}; 2 + use crate::utils::DateTimeExt; 2 3 use lexica::app_bsky::actor::ProfileView; 3 4 use lexica::app_bsky::labeler::{ 4 5 LabelerPolicy, LabelerView, LabelerViewDetailed, LabelerViewerState, ··· 30 31 like_count: likes.unwrap_or_default() as i64, 31 32 viewer, 32 33 labels: map_labels(labels), 33 - indexed_at: labeler.indexed_at.and_utc(), 34 + indexed_at: labeler.indexed_at.into_jacquard(), 34 35 } 35 36 } 36 37 ··· 94 95 subject_types, 95 96 subject_collections, 96 97 labels: map_labels(labels), 97 - indexed_at: labeler.indexed_at.and_utc(), 98 + indexed_at: labeler.indexed_at.into_jacquard(), 98 99 } 99 100 } 100 101
+3 -2
crates/parakeet/src/hydration/list.rs
··· 1 1 use crate::db::ListStateRet; 2 2 use crate::hydration::{map_labels, StatefulHydrator}; 3 + use crate::utils::DateTimeExt; 3 4 use crate::xrpc::cdn::BskyCdn; 4 5 use lexica::app_bsky::actor::ProfileView; 5 6 use lexica::app_bsky::graph::{ListPurpose, ListView, ListViewBasic, ListViewerState}; ··· 34 35 list_item_count, 35 36 viewer, 36 37 labels: map_labels(labels), 37 - indexed_at: list.created_at, 38 + indexed_at: list.created_at.into_jacquard(), 38 39 }) 39 40 } 40 41 ··· 65 66 list_item_count, 66 67 viewer, 67 68 labels: map_labels(labels), 68 - indexed_at: list.created_at, 69 + indexed_at: list.created_at.into_jacquard(), 69 70 }) 70 71 } 71 72
+3 -2
crates/parakeet/src/hydration/posts.rs
··· 1 1 use crate::db::PostStateRet; 2 2 use crate::hydration::{map_labels, StatefulHydrator}; 3 + use crate::utils::DateTimeExt; 3 4 use lexica::app_bsky::actor::ProfileViewBasic; 4 5 use lexica::app_bsky::embed::Embed; 5 6 use lexica::app_bsky::feed::{ ··· 66 67 labels: map_labels(labels), 67 68 viewer, 68 69 threadgate, 69 - indexed_at: post.created_at, 70 + indexed_at: post.created_at.into_jacquard(), 70 71 } 71 72 } 72 73 ··· 283 284 by: profiles_hydrated.get(&by).cloned()?, 284 285 uri: Some(uri), 285 286 cid: None, 286 - indexed_at: at, 287 + indexed_at: at.into_jacquard(), 287 288 })) 288 289 } 289 290 RawFeedItem::Pin { .. } => Some(FeedViewPostReason::Pin),
+9 -9
crates/parakeet/src/hydration/profile.rs
··· 1 1 use crate::db::ProfileStateRet; 2 2 use crate::hydration::map_labels; 3 3 use crate::loaders::ProfileLoaderRet; 4 + use crate::utils::DateTimeExt; 4 5 use crate::xrpc::cdn::BskyCdn; 5 - use chrono::prelude::*; 6 - use chrono::TimeDelta; 6 + use chrono::{prelude::*, TimeDelta}; 7 7 use lexica::app_bsky::actor::*; 8 8 use lexica::app_bsky::embed::External; 9 9 use lexica::app_bsky::graph::ListViewBasic; ··· 85 85 issuer: entry.verifier, 86 86 uri: entry.at_uri, 87 87 is_valid, 88 - created_at: entry.created_at, 88 + created_at: entry.created_at.into_jacquard(), 89 89 } 90 90 }) 91 91 .collect() ··· 176 176 status: s, 177 177 record: status.record, 178 178 embed, 179 - expires_at, 179 + expires_at: expires_at.map(|v| v.into_jacquard()), 180 180 is_active, 181 181 }) 182 182 } ··· 205 205 verification, 206 206 status, 207 207 pronouns: profile.pronouns, 208 - created_at: profile.created_at.and_utc(), 208 + created_at: profile.created_at.into_jacquard(), 209 209 } 210 210 } 211 211 ··· 234 234 verification, 235 235 status, 236 236 pronouns: profile.pronouns, 237 - created_at: profile.created_at.and_utc(), 238 - indexed_at: profile.indexed_at, 237 + created_at: profile.created_at.into_jacquard(), 238 + indexed_at: profile.indexed_at.into_jacquard(), 239 239 } 240 240 } 241 241 ··· 269 269 status, 270 270 pronouns: profile.pronouns, 271 271 website: profile.website, 272 - created_at: profile.created_at.and_utc(), 273 - indexed_at: profile.indexed_at, 272 + created_at: profile.created_at.into_jacquard(), 273 + indexed_at: profile.indexed_at.into_jacquard(), 274 274 } 275 275 } 276 276
+3 -2
crates/parakeet/src/hydration/starter_packs.rs
··· 1 1 use crate::hydration::{map_labels, StatefulHydrator}; 2 + use crate::utils::DateTimeExt; 2 3 use lexica::app_bsky::actor::ProfileViewBasic; 3 4 use lexica::app_bsky::feed::GeneratorView; 4 5 use lexica::app_bsky::graph::{ListViewBasic, StarterPackView, StarterPackViewBasic}; ··· 21 22 joined_week_count: 0, 22 23 joined_all_time_count: 0, 23 24 labels: map_labels(labels), 24 - indexed_at: starter_pack.created_at, 25 + indexed_at: starter_pack.created_at.into_jacquard(), 25 26 } 26 27 } 27 28 ··· 46 47 joined_week_count: 0, 47 48 joined_all_time_count: 0, 48 49 labels: map_labels(labels), 49 - indexed_at: starter_pack.created_at, 50 + indexed_at: starter_pack.created_at.into_jacquard(), 50 51 } 51 52 } 52 53
+1
crates/parakeet/src/main.rs
··· 18 18 mod hydration; 19 19 mod instrumentation; 20 20 mod loaders; 21 + mod utils; 21 22 mod xrpc; 22 23 23 24 #[derive(Clone)]
+20
crates/parakeet/src/utils.rs
··· 1 + use chrono::{DateTime, NaiveDateTime, Utc}; 2 + use jacquard_common::types::string::Datetime; 3 + 4 + pub trait DateTimeExt { 5 + fn into_jacquard(self) -> Datetime; 6 + } 7 + 8 + impl DateTimeExt for DateTime<Utc> { 9 + #[inline(always)] 10 + fn into_jacquard(self) -> Datetime { 11 + Datetime::new(self.fixed_offset()) 12 + } 13 + } 14 + 15 + impl DateTimeExt for NaiveDateTime { 16 + #[inline(always)] 17 + fn into_jacquard(self) -> Datetime { 18 + self.and_utc().into_jacquard() 19 + } 20 + }
+2 -1
crates/parakeet/src/xrpc/app_bsky/bookmark.rs
··· 1 1 use crate::hydration::StatefulHydrator; 2 + use crate::utils::DateTimeExt; 2 3 use crate::xrpc::error::XrpcResult; 3 4 use crate::xrpc::extract::{AtpAcceptLabelers, AtpAuth}; 4 5 use crate::xrpc::{datetime_cursor, CursorQuery}; ··· 138 139 Some(BookmarkView { 139 140 subject, 140 141 item, 141 - created_at: bookmark.created_at, 142 + created_at: bookmark.created_at.into_jacquard(), 142 143 }) 143 144 }) 144 145 .collect();
+3 -2
crates/parakeet/src/xrpc/app_bsky/feed/likes.rs
··· 1 1 use crate::hydration::posts::RawFeedItem; 2 2 use crate::hydration::StatefulHydrator; 3 + use crate::utils::DateTimeExt; 3 4 use crate::xrpc::error::{Error, XrpcResult}; 4 5 use crate::xrpc::extract::{AtpAcceptLabelers, AtpAuth}; 5 6 use crate::xrpc::{datetime_cursor, normalise_at_uri, ActorWithCursorQuery}; ··· 140 141 141 142 Some(Like { 142 143 actor, 143 - created_at, 144 - indexed_at: indexed_at.and_utc(), 144 + created_at: created_at.into_jacquard(), 145 + indexed_at: indexed_at.into_jacquard(), 145 146 }) 146 147 }) 147 148 .collect();
+2 -1
crates/parakeet/src/xrpc/community_lexicon/bookmarks.rs
··· 1 + use crate::utils::DateTimeExt; 1 2 use crate::xrpc::datetime_cursor; 2 3 use crate::xrpc::error::XrpcResult; 3 4 use crate::xrpc::extract::AtpAuth; ··· 61 62 .map(|bookmark| Bookmark { 62 63 subject: bookmark.subject, 63 64 tags: bookmark.tags.into(), 64 - created_at: bookmark.created_at, 65 + created_at: bookmark.created_at.into_jacquard(), 65 66 }) 66 67 .collect(); 67 68