···73737474/// Persist the projection cursor inside an in-flight projection transaction.
7575pub async fn save_cursor(tx: &mut Transaction<'_, Sqlite>, cursor: u64) -> anyhow::Result<()> {
7676- let now = chrono::Utc::now().timestamp_millis();
7676+ let now = chrono::Utc::now()
7777+ .timestamp_nanos_opt()
7878+ .expect("current timestamp must fit in i64 nanoseconds");
7779 sqlx::query!(
7880 "INSERT INTO projection_state (key, value, updated_at) VALUES ('cursor', ?, ?) \
7981 ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at",
+18-16
src/indexing/projection.rs
···211211 .unwrap_or_default()
212212}
213213214214-fn parse_millis(v: &Value, key: &str) -> Option<i64> {
214214+fn parse_nanos(v: &Value, key: &str) -> Option<i64> {
215215 chrono::DateTime::parse_from_rfc3339(v.get(key)?.as_str()?)
216216 .ok()
217217- .map(|dt| dt.timestamp_millis())
217217+ .and_then(|dt| dt.timestamp_nanos_opt())
218218}
219219220220-fn now_millis() -> i64 {
221221- chrono::Utc::now().timestamp_millis()
220220+fn now_nanos() -> i64 {
221221+ chrono::Utc::now()
222222+ .timestamp_nanos_opt()
223223+ .expect("current timestamp must fit in i64 nanoseconds")
222224}
223225224226fn at_uri(did: &str, collection: &str, rkey: &str) -> String {
···263265 Some(tags.join(" "))
264266 };
265267266266- let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis);
267267- let indexed_at = now_millis();
268268+ let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos);
269269+ let indexed_at = now_nanos();
268270 // The full raw record, so read endpoints can populate the required `record`
269271 // field on thingView directly from SQLite (and the write path gets
270272 // read-your-own-write, since Hydrant has no public single-record write API).
···370372 return Ok(());
371373 };
372374 let summary = s_str(rec, "summary");
373373- let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis);
374374- let indexed_at = now_millis();
375375+ let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos);
376376+ let indexed_at = now_nanos();
375377 let record_json = rec.to_string();
376378377379 sqlx::query!(
···446448 };
447449 let format = s_str(rec, "format");
448450 let file_json = rec.get("file").map(|v| v.to_string());
449449- let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis);
450450- let indexed_at = now_millis();
451451+ let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos);
452452+ let indexed_at = now_nanos();
451453 let record_json = rec.to_string();
452454453455 sqlx::query!(
···519521 .and_then(|v| v.as_array())
520522 .map(|a| serde_json::to_string(a).unwrap_or_else(|_| "[]".to_string()));
521523 let record_json = rec.to_string();
522522- let indexed_at = now_millis();
524524+ let indexed_at = now_nanos();
523525524526 sqlx::query!(
525527 "INSERT INTO profiles \
···565567 tx: &mut Transaction<'_, Sqlite>,
566568 e: &IdentityProjection,
567569) -> anyhow::Result<()> {
568568- let updated_at = now_millis();
570570+ let updated_at = now_nanos();
569571 sqlx::query!(
570572 "INSERT INTO identities (did, handle, updated_at) VALUES (?, ?, ?) \
571573 ON CONFLICT(did) DO UPDATE SET handle = excluded.handle, \
···598600 let Some(subject) = ref_uri(rec, "subject") else {
599601 return Ok(());
600602 };
601601- let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis);
603603+ let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos);
602604603605 let res = sqlx::query!(
604606 "INSERT OR IGNORE INTO likes (did, rkey, subject_uri, created_at) VALUES (?, ?, ?, ?)",
···667669 return Ok(());
668670 };
669671 let note = s_str(rec, "note");
670670- let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis);
672672+ let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos);
671673672674 let res = sqlx::query!(
673675 "INSERT OR IGNORE INTO saves (did, rkey, subject_uri, note, created_at) \
···740742 let Some(tag) = s_str(rec, "tag") else {
741743 return Ok(());
742744 };
743743- let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis);
745745+ let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos);
744746745747 let res = sqlx::query!(
746748 "INSERT OR IGNORE INTO tags (did, rkey, subject_uri, tag, created_at) \
···809811 let Some(subject_uri) = ref_uri(rec, "subject") else {
810812 return Ok(());
811813 };
812812- let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis);
814814+ let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos);
813815814816 // No denormalized count for listitems; list membership is queried directly.
815817 sqlx::query!(
+6-2
src/oauth/mod.rs
···8484 }
85858686 async fn upsert_session(&self, session: ClientSessionData) -> Result<(), SessionStoreError> {
8787- let now = chrono::Utc::now().timestamp_millis();
8787+ let now = chrono::Utc::now()
8888+ .timestamp_nanos_opt()
8989+ .expect("current timestamp must fit in i64 nanoseconds");
8890 let payload = serde_json::to_string(&session)?;
8991 sqlx::query!(
9092 "INSERT INTO oauth_sessions
···154156 &self,
155157 auth_req_info: &AuthRequestData,
156158 ) -> Result<(), SessionStoreError> {
157157- let now = chrono::Utc::now().timestamp_millis();
159159+ let now = chrono::Utc::now()
160160+ .timestamp_nanos_opt()
161161+ .expect("current timestamp must fit in i64 nanoseconds");
158162 let payload = serde_json::to_string(auth_req_info)?;
159163 sqlx::query!(
160164 "INSERT INTO oauth_auth_requests