tangled
alpha
login
or
join now
parakeet.at
/
parakeet
63
fork
atom
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
63
fork
atom
overview
issues
12
pulls
pipelines
fix: datetimes
mia.omg.lol
1 week ago
a3926001
13893e3f
verified
This commit was signed with the committer's
known signature
.
mia.omg.lol
SSH Key Fingerprint:
SHA256:eb+NhC0QEl+XKRuFP/97oH6LEz0TXTKPXGDIAI5y7CQ=
+82
-51
21 changed files
expand all
collapse all
unified
split
Cargo.lock
crates
consumer
src
db
record.rs
lexica
Cargo.toml
src
app_bsky
actor.rs
bookmark.rs
embed.rs
feed.rs
graph.rs
labeler.rs
community_lexicon
bookmarks.rs
parakeet
src
hydration
feedgen.rs
labeler.rs
list.rs
posts.rs
profile.rs
starter_packs.rs
main.rs
utils.rs
xrpc
app_bsky
bookmark.rs
feed
likes.rs
community_lexicon
bookmarks.rs
+1
Cargo.lock
···
2560
2560
dependencies = [
2561
2561
"chrono",
2562
2562
"cid",
2563
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
47
-
&rec.created_at,
47
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
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
4
-
use chrono::prelude::*;
4
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
166
-
pub created_at: DateTime<Utc>,
166
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
194
-
pub created_at: DateTime<Utc>,
195
195
-
pub indexed_at: NaiveDateTime,
194
194
+
pub created_at: Datetime,
195
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
233
-
pub created_at: DateTime<Utc>,
234
234
-
pub indexed_at: NaiveDateTime,
233
233
+
pub created_at: Datetime,
234
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
243
-
pub created_at: DateTime<Utc>,
243
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
278
-
pub expires_at: Option<DateTime<Utc>>,
278
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
3
-
use chrono::prelude::*;
3
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
11
-
pub created_at: DateTime<Utc>,
11
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
7
-
use chrono::prelude::*;
7
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
109
-
pub indexed_at: DateTime<Utc>,
109
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
7
-
use chrono::prelude::*;
7
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
45
-
pub indexed_at: DateTime<Utc>,
45
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
105
-
pub indexed_at: DateTime<Utc>,
105
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
177
-
pub indexed_at: DateTime<Utc>,
177
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
213
-
pub created_at: DateTime<Utc>,
214
214
-
pub indexed_at: DateTime<Utc>,
213
213
+
pub created_at: Datetime,
214
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
5
-
use chrono::prelude::*;
5
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
34
-
pub indexed_at: DateTime<Utc>,
34
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
60
-
pub indexed_at: DateTime<Utc>,
60
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
116
-
pub indexed_at: DateTime<Utc>,
116
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
133
-
pub indexed_at: DateTime<Utc>,
133
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
4
-
use chrono::prelude::*;
4
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
26
-
pub indexed_at: DateTime<Utc>,
26
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
49
-
pub indexed_at: DateTime<Utc>,
49
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
1
-
use chrono::prelude::*;
1
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
13
-
pub created_at: DateTime<Utc>,
13
13
+
pub created_at: Datetime,
14
14
}
+2
-1
crates/parakeet/src/hydration/feedgen.rs
···
1
1
use crate::hydration::map_labels;
2
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
48
-
indexed_at: feedgen.created_at,
49
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
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
33
-
indexed_at: labeler.indexed_at.and_utc(),
34
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
97
-
indexed_at: labeler.indexed_at.and_utc(),
98
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
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
37
-
indexed_at: list.created_at,
38
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
68
-
indexed_at: list.created_at,
69
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
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
69
-
indexed_at: post.created_at,
70
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
286
-
indexed_at: at,
287
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
4
+
use crate::utils::DateTimeExt;
4
5
use crate::xrpc::cdn::BskyCdn;
5
5
-
use chrono::prelude::*;
6
6
-
use chrono::TimeDelta;
6
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
88
-
created_at: entry.created_at,
88
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
179
-
expires_at,
179
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
208
-
created_at: profile.created_at.and_utc(),
208
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
237
-
created_at: profile.created_at.and_utc(),
238
238
-
indexed_at: profile.indexed_at,
237
237
+
created_at: profile.created_at.into_jacquard(),
238
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
272
-
created_at: profile.created_at.and_utc(),
273
273
-
indexed_at: profile.indexed_at,
272
272
+
created_at: profile.created_at.into_jacquard(),
273
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
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
24
-
indexed_at: starter_pack.created_at,
25
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
49
-
indexed_at: starter_pack.created_at,
50
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
21
+
mod utils;
21
22
mod xrpc;
22
23
23
24
#[derive(Clone)]
+20
crates/parakeet/src/utils.rs
···
1
1
+
use chrono::{DateTime, NaiveDateTime, Utc};
2
2
+
use jacquard_common::types::string::Datetime;
3
3
+
4
4
+
pub trait DateTimeExt {
5
5
+
fn into_jacquard(self) -> Datetime;
6
6
+
}
7
7
+
8
8
+
impl DateTimeExt for DateTime<Utc> {
9
9
+
#[inline(always)]
10
10
+
fn into_jacquard(self) -> Datetime {
11
11
+
Datetime::new(self.fixed_offset())
12
12
+
}
13
13
+
}
14
14
+
15
15
+
impl DateTimeExt for NaiveDateTime {
16
16
+
#[inline(always)]
17
17
+
fn into_jacquard(self) -> Datetime {
18
18
+
self.and_utc().into_jacquard()
19
19
+
}
20
20
+
}
+2
-1
crates/parakeet/src/xrpc/app_bsky/bookmark.rs
···
1
1
use crate::hydration::StatefulHydrator;
2
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
141
-
created_at: bookmark.created_at,
142
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
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
143
-
created_at,
144
144
-
indexed_at: indexed_at.and_utc(),
144
144
+
created_at: created_at.into_jacquard(),
145
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
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
64
-
created_at: bookmark.created_at,
65
65
+
created_at: bookmark.created_at.into_jacquard(),
65
66
})
66
67
.collect();
67
68