atproto Thingiverse but good
12

Configure Feed

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

timestamps: nanosecond precision throughout

Orual (Jun 29, 2026, 7:06 PM EDT) 5ba4dc88 90a45973

+59 -36
+20 -6
src/appview/tests.rs
··· 79 79 like_count: i64, 80 80 ) -> String { 81 81 let uri = format!("at://{did}/space.polymodel.library.thing/{rkey}"); 82 - let now = chrono::Utc::now().timestamp_millis(); 83 - let created = now - 86_400_000; // 1 day ago by default 82 + let now = chrono::Utc::now() 83 + .timestamp_nanos_opt() 84 + .expect("current timestamp must fit in i64 nanoseconds"); 85 + let created = now - 86_400_000_000_000; // 1 day ago by default 84 86 sqlx::query( 85 87 r#"INSERT INTO things (did, rkey, uri, cid, name, summary, license, tags_json, 86 88 tags_text, instructions_text, cover_json, derived_from_uri, created_at, indexed_at, record_json) ··· 134 136 parts: &[&str], 135 137 ) -> String { 136 138 let uri = format!("at://{did}/space.polymodel.library.model/{rkey}"); 137 - let now = chrono::Utc::now().timestamp_millis(); 139 + let now = chrono::Utc::now() 140 + .timestamp_nanos_opt() 141 + .expect("current timestamp must fit in i64 nanoseconds"); 138 142 sqlx::query( 139 143 r#"INSERT INTO models (did, rkey, uri, cid, name, summary, created_at, indexed_at, record_json) 140 144 VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?)"#, ··· 172 176 173 177 async fn seed_part(pool: &SqlitePool, did: &str, rkey: &str, name: &str) -> String { 174 178 let uri = format!("at://{did}/space.polymodel.library.part/{rkey}"); 175 - let now = chrono::Utc::now().timestamp_millis(); 179 + let now = chrono::Utc::now() 180 + .timestamp_nanos_opt() 181 + .expect("current timestamp must fit in i64 nanoseconds"); 176 182 sqlx::query( 177 183 r#"INSERT INTO parts (did, rkey, uri, cid, name, format, file_json, created_at, indexed_at, record_json) 178 184 VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)"#, ··· 204 210 ) 205 211 .bind(did) 206 212 .bind(handle) 207 - .bind(chrono::Utc::now().timestamp_millis()) 213 + .bind( 214 + chrono::Utc::now() 215 + .timestamp_nanos_opt() 216 + .expect("current timestamp must fit in i64 nanoseconds"), 217 + ) 208 218 .execute(pool) 209 219 .await 210 220 .unwrap(); ··· 223 233 .bind(display) 224 234 .bind(avatar) 225 235 .bind(json!({"displayName": display, "avatar": blob("bafkreiavatar")}).to_string()) 226 - .bind(chrono::Utc::now().timestamp_millis()) 236 + .bind( 237 + chrono::Utc::now() 238 + .timestamp_nanos_opt() 239 + .expect("current timestamp must fit in i64 nanoseconds"), 240 + ) 227 241 .execute(pool) 228 242 .await 229 243 .unwrap();
+12 -11
src/appview/views.rs
··· 55 55 fn handle_of(handle: String) -> AppResult<Handle> { 56 56 Handle::new_owned(handle).map_err(|e| internal(format!("invalid handle: {e}"))) 57 57 } 58 - fn datetime_millis(millis: i64) -> Datetime { 59 - let dt = chrono::DateTime::<chrono::Utc>::from_timestamp_millis(millis) 60 - .unwrap_or_else(chrono::Utc::now); 58 + fn datetime_nanos(nanos: i64) -> Datetime { 59 + let dt = chrono::DateTime::<chrono::Utc>::from_timestamp_nanos(nanos); 61 60 Datetime::new(dt.fixed_offset()) 62 61 } 63 62 ··· 332 331 cid: s(row.cid), 333 332 author, 334 333 record: record_data(row.record_json.as_deref())?, 335 - indexed_at: datetime_millis(row.indexed_at), 334 + indexed_at: datetime_nanos(row.indexed_at), 336 335 name: s(row.name), 337 336 summary: row.summary.map(s), 338 337 license: s(row.license), ··· 361 360 cid: s(row.cid), 362 361 name: s(row.name), 363 362 author: actor_for_did(state, &row.did).await?, 364 - indexed_at: datetime_millis(row.indexed_at), 363 + indexed_at: datetime_nanos(row.indexed_at), 365 364 like_count: row.like_count, 366 365 save_count: row.save_count, 367 366 model_count: row.model_count, ··· 387 386 cid: s(row.cid), 388 387 author, 389 388 record: record_data(row.record_json.as_deref())?, 390 - indexed_at: datetime_millis(row.indexed_at), 389 + indexed_at: datetime_nanos(row.indexed_at), 391 390 name: s(row.name), 392 391 summary: row.summary.map(s), 393 392 part_count: row.part_count, ··· 411 410 cid: s(row.cid), 412 411 author, 413 412 record: record_data(row.record_json.as_deref())?, 414 - indexed_at: datetime_millis(row.indexed_at), 413 + indexed_at: datetime_nanos(row.indexed_at), 415 414 name: s(row.name), 416 415 format: row.format.map(s), 417 416 file: file_data(Some(row.file_json.as_str()))?, ··· 691 690 .fetch_all(&state.pool) 692 691 .await, 693 692 )?; 694 - let now = chrono::Utc::now().timestamp_millis(); 693 + let now = chrono::Utc::now() 694 + .timestamp_nanos_opt() 695 + .expect("current timestamp must fit in i64 nanoseconds"); 695 696 let mut scored: Vec<(f64, ThingRow)> = 696 697 rows.into_iter().map(|r| (hot_score(&r, now), r)).collect(); 697 698 // score desc, tie-break rkey desc (deterministic, reproducible page boundary). ··· 715 716 feed_view(state, page, cursor, viewer_did).await 716 717 } 717 718 718 - fn hot_score(row: &ThingRow, now_millis: i64) -> f64 { 719 - let age_hours = ((now_millis - row.created_at).max(0) as f64) / 3_600_000.0; 719 + fn hot_score(row: &ThingRow, now_nanos: i64) -> f64 { 720 + let age_hours = ((now_nanos - row.created_at).max(0) as f64) / 3_600_000_000_000.0; 720 721 let engagement = row.like_count as f64 + row.save_count as f64; 721 722 engagement / (age_hours + 2.0).powf(1.5) 722 723 } ··· 950 951 links: uri_vec(row.links_json.as_deref()), 951 952 thing_count: Some(stats.thing_count), 952 953 total_likes: Some(stats.total_likes), 953 - indexed_at: Some(datetime_millis(row.indexed_at)), 954 + indexed_at: Some(datetime_nanos(row.indexed_at)), 954 955 extra_data: None, 955 956 })) 956 957 }
+3 -1
src/indexing/db.rs
··· 73 73 74 74 /// Persist the projection cursor inside an in-flight projection transaction. 75 75 pub async fn save_cursor(tx: &mut Transaction<'_, Sqlite>, cursor: u64) -> anyhow::Result<()> { 76 - let now = chrono::Utc::now().timestamp_millis(); 76 + let now = chrono::Utc::now() 77 + .timestamp_nanos_opt() 78 + .expect("current timestamp must fit in i64 nanoseconds"); 77 79 sqlx::query!( 78 80 "INSERT INTO projection_state (key, value, updated_at) VALUES ('cursor', ?, ?) \ 79 81 ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at",
+18 -16
src/indexing/projection.rs
··· 211 211 .unwrap_or_default() 212 212 } 213 213 214 - fn parse_millis(v: &Value, key: &str) -> Option<i64> { 214 + fn parse_nanos(v: &Value, key: &str) -> Option<i64> { 215 215 chrono::DateTime::parse_from_rfc3339(v.get(key)?.as_str()?) 216 216 .ok() 217 - .map(|dt| dt.timestamp_millis()) 217 + .and_then(|dt| dt.timestamp_nanos_opt()) 218 218 } 219 219 220 - fn now_millis() -> i64 { 221 - chrono::Utc::now().timestamp_millis() 220 + fn now_nanos() -> i64 { 221 + chrono::Utc::now() 222 + .timestamp_nanos_opt() 223 + .expect("current timestamp must fit in i64 nanoseconds") 222 224 } 223 225 224 226 fn at_uri(did: &str, collection: &str, rkey: &str) -> String { ··· 263 265 Some(tags.join(" ")) 264 266 }; 265 267 266 - let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis); 267 - let indexed_at = now_millis(); 268 + let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos); 269 + let indexed_at = now_nanos(); 268 270 // The full raw record, so read endpoints can populate the required `record` 269 271 // field on thingView directly from SQLite (and the write path gets 270 272 // read-your-own-write, since Hydrant has no public single-record write API). ··· 370 372 return Ok(()); 371 373 }; 372 374 let summary = s_str(rec, "summary"); 373 - let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis); 374 - let indexed_at = now_millis(); 375 + let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos); 376 + let indexed_at = now_nanos(); 375 377 let record_json = rec.to_string(); 376 378 377 379 sqlx::query!( ··· 446 448 }; 447 449 let format = s_str(rec, "format"); 448 450 let file_json = rec.get("file").map(|v| v.to_string()); 449 - let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis); 450 - let indexed_at = now_millis(); 451 + let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos); 452 + let indexed_at = now_nanos(); 451 453 let record_json = rec.to_string(); 452 454 453 455 sqlx::query!( ··· 519 521 .and_then(|v| v.as_array()) 520 522 .map(|a| serde_json::to_string(a).unwrap_or_else(|_| "[]".to_string())); 521 523 let record_json = rec.to_string(); 522 - let indexed_at = now_millis(); 524 + let indexed_at = now_nanos(); 523 525 524 526 sqlx::query!( 525 527 "INSERT INTO profiles \ ··· 565 567 tx: &mut Transaction<'_, Sqlite>, 566 568 e: &IdentityProjection, 567 569 ) -> anyhow::Result<()> { 568 - let updated_at = now_millis(); 570 + let updated_at = now_nanos(); 569 571 sqlx::query!( 570 572 "INSERT INTO identities (did, handle, updated_at) VALUES (?, ?, ?) \ 571 573 ON CONFLICT(did) DO UPDATE SET handle = excluded.handle, \ ··· 598 600 let Some(subject) = ref_uri(rec, "subject") else { 599 601 return Ok(()); 600 602 }; 601 - let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis); 603 + let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos); 602 604 603 605 let res = sqlx::query!( 604 606 "INSERT OR IGNORE INTO likes (did, rkey, subject_uri, created_at) VALUES (?, ?, ?, ?)", ··· 667 669 return Ok(()); 668 670 }; 669 671 let note = s_str(rec, "note"); 670 - let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis); 672 + let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos); 671 673 672 674 let res = sqlx::query!( 673 675 "INSERT OR IGNORE INTO saves (did, rkey, subject_uri, note, created_at) \ ··· 740 742 let Some(tag) = s_str(rec, "tag") else { 741 743 return Ok(()); 742 744 }; 743 - let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis); 745 + let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos); 744 746 745 747 let res = sqlx::query!( 746 748 "INSERT OR IGNORE INTO tags (did, rkey, subject_uri, tag, created_at) \ ··· 809 811 let Some(subject_uri) = ref_uri(rec, "subject") else { 810 812 return Ok(()); 811 813 }; 812 - let created_at = parse_millis(rec, "createdAt").unwrap_or_else(now_millis); 814 + let created_at = parse_nanos(rec, "createdAt").unwrap_or_else(now_nanos); 813 815 814 816 // No denormalized count for listitems; list membership is queried directly. 815 817 sqlx::query!(
+6 -2
src/oauth/mod.rs
··· 84 84 } 85 85 86 86 async fn upsert_session(&self, session: ClientSessionData) -> Result<(), SessionStoreError> { 87 - let now = chrono::Utc::now().timestamp_millis(); 87 + let now = chrono::Utc::now() 88 + .timestamp_nanos_opt() 89 + .expect("current timestamp must fit in i64 nanoseconds"); 88 90 let payload = serde_json::to_string(&session)?; 89 91 sqlx::query!( 90 92 "INSERT INTO oauth_sessions ··· 154 156 &self, 155 157 auth_req_info: &AuthRequestData, 156 158 ) -> Result<(), SessionStoreError> { 157 - let now = chrono::Utc::now().timestamp_millis(); 159 + let now = chrono::Utc::now() 160 + .timestamp_nanos_opt() 161 + .expect("current timestamp must fit in i64 nanoseconds"); 158 162 let payload = serde_json::to_string(auth_req_info)?; 159 163 sqlx::query!( 160 164 "INSERT INTO oauth_auth_requests