From 3a6bcbd94d5f318b823ed9bfe4575624e69c92e5 Mon Sep 17 00:00:00 2001 From: goose Date: Thu, 26 Feb 2026 09:22:36 -0300 Subject: [PATCH] Fix MongoDB DateTime serialization issues - Replace chrono::DateTime with mongodb::bson::DateTime in models - Update API responses to use timestamp_millis() for JSON serialization - Fix User, Share model DateTime fields - Update all handler responses to return i64 timestamps - This fixes the Kind: invalid type: map, expected RFC 3339 error --- backend/src/handlers/health.rs | 5 ++++- backend/src/handlers/shares.rs | 12 ++++++------ backend/src/handlers/users.rs | 8 ++++---- backend/src/models/share.rs | 11 ++++++----- backend/src/models/user.rs | 9 +++++---- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/backend/src/handlers/health.rs b/backend/src/handlers/health.rs index 9f6d025..2964c14 100644 --- a/backend/src/handlers/health.rs +++ b/backend/src/handlers/health.rs @@ -9,10 +9,13 @@ pub async fn health_check(State(state): State) -> Json { "error" }; + // Use timestamp_millis for consistency with other endpoints + let timestamp = mongodb::bson::DateTime::now().timestamp_millis(); + Json(json!({ "status": "ok", "database": status, - "timestamp": chrono::Utc::now().to_rfc3339() + "timestamp": timestamp })) } diff --git a/backend/src/handlers/shares.rs b/backend/src/handlers/shares.rs index ef1592b..6acdb7f 100644 --- a/backend/src/handlers/shares.rs +++ b/backend/src/handlers/shares.rs @@ -32,8 +32,8 @@ pub struct ShareResponse { pub resource_type: String, pub resource_id: Option, pub permissions: Vec, - pub expires_at: Option, - pub created_at: String, + pub expires_at: Option, + pub created_at: i64, pub active: bool, } @@ -47,8 +47,8 @@ impl TryFrom for ShareResponse { resource_type: share.resource_type, resource_id: share.resource_id.map(|id| id.to_string()), permissions: share.permissions.into_iter().map(|p| p.to_string()).collect(), - expires_at: share.expires_at.map(|dt| dt.to_rfc3339()), - created_at: share.created_at.to_rfc3339(), + expires_at: share.expires_at.map(|dt| dt.timestamp_millis()), + created_at: share.created_at.timestamp_millis(), active: share.active, }) } @@ -136,7 +136,7 @@ pub async fn create_share( // Calculate expiration let expires_at = req.expires_days.map(|days| { - chrono::Utc::now() + chrono::Duration::days(days as i64) + mongodb::bson::DateTime::now().saturating_add_millis(days as i64 * 24 * 60 * 60 * 1000) }); let share = Share::new( @@ -289,7 +289,7 @@ pub async fn update_share( } if let Some(days) = req.expires_days { - share.expires_at = Some(chrono::Utc::now() + chrono::Duration::days(days as i64)); + share.expires_at = Some(mongodb::bson::DateTime::now().saturating_add_millis(days as i64 * 24 * 60 * 60 * 1000)); } match state.db.update_share(&share).await { diff --git a/backend/src/handlers/users.rs b/backend/src/handlers/users.rs index 24fc303..9ef4e85 100644 --- a/backend/src/handlers/users.rs +++ b/backend/src/handlers/users.rs @@ -20,8 +20,8 @@ pub struct UserProfileResponse { pub id: String, pub email: String, pub username: String, - pub created_at: String, - pub last_active: String, + pub created_at: i64, + pub last_active: i64, pub email_verified: bool, } @@ -33,8 +33,8 @@ impl TryFrom for UserProfileResponse { id: user.id.map(|id| id.to_string()).unwrap_or_default(), email: user.email, username: user.username, - created_at: user.created_at.to_rfc3339(), - last_active: user.last_active.to_rfc3339(), + created_at: user.created_at.timestamp_millis(), + last_active: user.last_active.timestamp_millis(), email_verified: user.email_verified, }) } diff --git a/backend/src/models/share.rs b/backend/src/models/share.rs index b1699df..c2762c5 100644 --- a/backend/src/models/share.rs +++ b/backend/src/models/share.rs @@ -1,6 +1,7 @@ use mongodb::bson::{doc, oid::ObjectId}; use mongodb::Collection; use serde::{Deserialize, Serialize}; +use mongodb::bson::DateTime; use super::permission::Permission; @@ -15,8 +16,8 @@ pub struct Share { pub resource_id: Option, pub permissions: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub expires_at: Option>, - pub created_at: chrono::DateTime, + pub expires_at: Option, + pub created_at: DateTime, pub active: bool, } @@ -27,7 +28,7 @@ impl Share { resource_type: String, resource_id: Option, permissions: Vec, - expires_at: Option>, + expires_at: Option, ) -> Self { Self { id: None, @@ -37,14 +38,14 @@ impl Share { resource_id, permissions, expires_at, - created_at: chrono::Utc::now(), + created_at: DateTime::now(), active: true, } } pub fn is_expired(&self) -> bool { if let Some(expires) = self.expires_at { - chrono::Utc::now() > expires + DateTime::now() > expires } else { false } diff --git a/backend/src/models/user.rs b/backend/src/models/user.rs index 8bb2320..d5db98d 100644 --- a/backend/src/models/user.rs +++ b/backend/src/models/user.rs @@ -2,6 +2,7 @@ use mongodb::bson::{doc, oid::ObjectId}; use mongodb::Collection; use serde::{Deserialize, Serialize}; +use mongodb::bson::DateTime; use crate::auth::password::verify_password; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -26,10 +27,10 @@ pub struct User { pub token_version: i32, /// When the user was created - pub created_at: chrono::DateTime, + pub created_at: DateTime, /// Last time the user was active - pub last_active: chrono::DateTime, + pub last_active: DateTime, /// Email verification status pub email_verified: bool, @@ -40,7 +41,7 @@ pub struct User { /// When the verification token expires #[serde(skip_serializing_if = "Option::is_none")] - pub verification_expires: Option>, + pub verification_expires: Option, } impl User { @@ -64,7 +65,7 @@ impl User { None }; - let now = chrono::Utc::now(); + let now = DateTime::now(); let recovery_enabled = recovery_phrase_hash.is_some(); Ok(User {