From 66a22457332724cd81402a1934ce197bc631a686 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sat, 6 Dec 2025 13:36:17 +0800 Subject: [PATCH] fix: proper datetime serialization --- Cargo.lock | 4 ++++ Cargo.toml | 6 +++--- src/admin/models.rs | 38 +++++++++++++++++++++++++++++++++----- src/db/models.rs | 45 ++++++++++++++++++++++++++++++++++++++------- 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63f52f7..837ff63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2138,6 +2138,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bytes", + "chrono", "crc", "crossbeam-queue", "either", @@ -2213,6 +2214,7 @@ dependencies = [ "bitflags", "byteorder", "bytes", + "chrono", "crc", "digest", "dotenvy", @@ -2254,6 +2256,7 @@ dependencies = [ "base64", "bitflags", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -2288,6 +2291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", diff --git a/Cargo.toml b/Cargo.toml index 9b68b35..b6452f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] axum = "0.8" tokio = { version = "1", features = ["full"] } -tower-http = { version = "0.6", features = ["compression-full", "fs"] } +tower-http = { version = "0.6", features = ["catch-panic", "compression-full", "fs"] } hyper = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_with = { version = "3.16", features = ["hashbrown_0_16", "json"] } @@ -20,12 +20,12 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } anyhow = "1" thiserror = "2" http-body-util = "0.1.1" -chrono = { version = "0.4", features = ["clock"] } +chrono = { version = "0.4", features = ["clock", "serde"] } reqwest = { version = "0.12", features = ["json", "rustls-tls", "gzip"], default-features = false } hashbrown = { version = "0.16", features = ["serde"] } concat-idents = "1.1" indoc = "2.0" -sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] } +sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono"] } rust-embed = "8.0" mime_guess = "2.0" jsonwebtoken = "9" diff --git a/src/admin/models.rs b/src/admin/models.rs index f66dfde..92c27f1 100644 --- a/src/admin/models.rs +++ b/src/admin/models.rs @@ -1,4 +1,7 @@ -use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +use chrono::{DateTime, TimeZone, Utc}; +use serde::{Deserialize, Serialize, Serializer}; use serde_json::Value; // Authentication models @@ -48,8 +51,10 @@ pub struct RuleResponse { pub action: String, pub custom_response: Option, pub is_enabled: bool, - pub created_at: String, - pub updated_at: String, + #[serde(serialize_with = "serialize_dt")] + pub created_at: DateTime, + #[serde(serialize_with = "serialize_dt")] + pub updated_at: DateTime, } impl From for RuleResponse { @@ -71,8 +76,10 @@ pub struct CommandResponse { pub id: i64, pub command: Value, pub status: String, - pub received_at: String, - pub processed_at: Option, + #[serde(serialize_with = "serialize_dt")] + pub received_at: DateTime, + #[serde(serialize_with = "serialize_option_dt")] + pub processed_at: Option>, pub notes: Option, } @@ -99,3 +106,24 @@ impl ApiError { Self { error: msg.into() } } } + +fn serialize_dt(dt: &DateTime, serializer: S) -> Result +where + TZ: TimeZone, + TZ::Offset: Display, + S: Serializer, +{ + let s = dt.format("%Y-%m-%d %H:%M:%S %z").to_string(); + serializer.serialize_str(&s) +} + +fn serialize_option_dt(dt: &Option>, serializer: S) -> Result +where + TZ: TimeZone, + TZ::Offset: Display, + S: Serializer, +{ + dt.as_ref() + .map(|dt| dt.format("%Y-%m-%d %H:%M:%S %z").to_string()) + .serialize(serializer) +} diff --git a/src/db/models.rs b/src/db/models.rs index 5d9a0de..1cc277d 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -1,4 +1,7 @@ -use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +use chrono::{DateTime, TimeZone, Utc}; +use serde::{Deserialize, Serialize, Serializer}; use serde_json::Value; #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] @@ -8,8 +11,10 @@ pub struct InterceptionRule { pub action: String, pub custom_response: Option, pub is_enabled: bool, - pub created_at: String, - pub updated_at: String, + #[serde(serialize_with = "serialize_dt")] + pub created_at: DateTime, + #[serde(serialize_with = "serialize_dt")] + pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] @@ -17,8 +22,11 @@ pub struct Command { pub id: i64, pub command_json: String, pub status: String, - pub received_at: String, - pub processed_at: Option, + #[serde(serialize_with = "serialize_dt")] + pub received_at: DateTime, + #[serde(serialize_with = "serialize_option_dt")] + #[serde(default)] + pub processed_at: Option>, pub notes: Option, } @@ -27,7 +35,8 @@ pub struct Config { pub key: String, pub value: String, pub description: Option, - pub updated_at: String, + #[serde(serialize_with = "serialize_dt")] + pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] @@ -38,5 +47,27 @@ pub struct RequestLog { pub response_body: Value, pub request_interception_action: String, pub response_interception_action: String, - pub created_at: String, + #[serde(serialize_with = "serialize_dt")] + pub created_at: DateTime, +} + +fn serialize_dt(dt: &DateTime, serializer: S) -> Result +where + TZ: TimeZone, + TZ::Offset: Display, + S: Serializer, +{ + let s = dt.format("%Y-%m-%d %H:%M:%S %z").to_string(); + serializer.serialize_str(&s) +} + +fn serialize_option_dt(dt: &Option>, serializer: S) -> Result +where + TZ: TimeZone, + TZ::Offset: Display, + S: Serializer, +{ + dt.as_ref() + .map(|dt| dt.format("%Y-%m-%d %H:%M:%S %z").to_string()) + .serialize(serializer) }