Skip to main content

recall_server/
lib.rs

1//! Recall's server half: SQLite persistence, LLM-assisted merge, and the
2//! HTTP API.
3//!
4//! The routes, status codes, JSON shapes and auth behavior are deliberately
5//! identical to the Node implementation this replaces, because during a
6//! migration a machine still on the old client and one already on this
7//! binary talk to the same deployment.
8
9#![deny(missing_docs)]
10
11pub mod config;
12pub mod merge;
13pub mod server;
14pub mod store;
15
16pub use config::{Config, ConfigError};
17pub use merge::Merger;
18pub use server::Server;
19pub use store::Store;
20
21use time::OffsetDateTime;
22
23/// A timestamp in the format every stored row and every API response uses:
24/// JavaScript's `Date.toISOString()` — millisecond precision, `Z` suffix,
25/// e.g. `2026-09-03T21:49:55.191Z`.
26///
27/// Rows written by the Node server are already in the database in this
28/// shape, and clients and the admin page display them, so the format is
29/// part of the compatibility surface rather than a style choice.
30pub fn now() -> String {
31    format_timestamp(OffsetDateTime::now_utc())
32}
33
34fn format_timestamp(at: OffsetDateTime) -> String {
35    // `[subsecond digits:3]` is load-bearing: it must render actual
36    // milliseconds, not three literal zeroes.
37    let fmt = time::macros::format_description!(
38        "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z"
39    );
40    at.to_offset(time::UtcOffset::UTC)
41        .format(&fmt)
42        .expect("the timestamp format is a compile-time constant")
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn timestamps_match_javascripts_toisostring() {
51        let t = now();
52        assert_eq!(t.len(), 24, "got {t}");
53        assert!(t.ends_with('Z'), "got {t}");
54        assert_eq!(&t[10..11], "T", "got {t}");
55        assert_eq!(&t[19..20], ".", "got {t}");
56        assert!(
57            t[20..23].chars().all(|c| c.is_ascii_digit()),
58            "milliseconds must be digits, got {t}"
59        );
60    }
61
62    /// The Go port had a bug where the layout rendered three literal zeroes
63    /// instead of milliseconds. Any real sub-second value must survive.
64    #[test]
65    fn renders_real_milliseconds_not_literal_zeroes() {
66        let at = OffsetDateTime::from_unix_timestamp_nanos(1_788_472_195_191_000_000).unwrap();
67        assert_eq!(format_timestamp(at), "2026-09-03T21:49:55.191Z");
68    }
69}