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

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