diff --git a/crates/crates_io_database/src/models/action.rs b/crates/crates_io_database/src/models/action.rs index cd7921dd0ec..893df2b418f 100644 --- a/crates/crates_io_database/src/models/action.rs +++ b/crates/crates_io_database/src/models/action.rs @@ -1,7 +1,7 @@ use crate::models::{ApiToken, User, Version}; use crate::schema::*; use bon::Builder; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use crates_io_diesel_helpers::pg_enum; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; @@ -49,7 +49,7 @@ pub struct VersionOwnerAction { pub user_id: i32, pub api_token_id: Option, pub action: VersionAction, - pub time: NaiveDateTime, + pub time: DateTime, } impl VersionOwnerAction { diff --git a/crates/crates_io_database/src/models/category.rs b/crates/crates_io_database/src/models/category.rs index d536913fffe..62287b954ab 100644 --- a/crates/crates_io_database/src/models/category.rs +++ b/crates/crates_io_database/src/models/category.rs @@ -1,6 +1,6 @@ use crate::models::Crate; use crate::schema::*; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::dsl; use diesel::prelude::*; use diesel_async::scoped_futures::ScopedFutureExt; @@ -15,7 +15,7 @@ pub struct Category { pub slug: String, pub description: String, pub crates_cnt: i32, - pub created_at: NaiveDateTime, + pub created_at: DateTime, } type WithSlug<'a> = dsl::Eq>; diff --git a/crates/crates_io_database/src/models/crate_owner_invitation.rs b/crates/crates_io_database/src/models/crate_owner_invitation.rs index da5a2e9c07b..24dd375e72f 100644 --- a/crates/crates_io_database/src/models/crate_owner_invitation.rs +++ b/crates/crates_io_database/src/models/crate_owner_invitation.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use diesel::prelude::*; use diesel_async::scoped_futures::ScopedFutureExt; use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; @@ -63,7 +63,7 @@ pub struct CrateOwnerInvitation { pub invited_user_id: i32, pub invited_by_user_id: i32, pub crate_id: i32, - pub created_at: NaiveDateTime, + pub created_at: DateTime, #[diesel(deserialize_as = String)] pub token: SecretString, pub expires_at: DateTime, diff --git a/crates/crates_io_database/src/models/email.rs b/crates/crates_io_database/src/models/email.rs index 6fa853b285f..c6fc64cf779 100644 --- a/crates/crates_io_database/src/models/email.rs +++ b/crates/crates_io_database/src/models/email.rs @@ -1,5 +1,5 @@ use bon::Builder; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; use secrecy::SecretString; @@ -16,7 +16,7 @@ pub struct Email { pub verified: bool, #[diesel(deserialize_as = String, serialize_as = String)] pub token: SecretString, - pub token_generated_at: Option, + pub token_generated_at: Option>, } #[derive(Debug, Insertable, AsChangeset, Builder)] diff --git a/crates/crates_io_database/src/models/keyword.rs b/crates/crates_io_database/src/models/keyword.rs index 1520c9308af..42675177561 100644 --- a/crates/crates_io_database/src/models/keyword.rs +++ b/crates/crates_io_database/src/models/keyword.rs @@ -1,4 +1,4 @@ -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::prelude::*; use diesel_async::scoped_futures::ScopedFutureExt; use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; @@ -12,7 +12,7 @@ pub struct Keyword { pub id: i32, pub keyword: String, pub crates_cnt: i32, - pub created_at: NaiveDateTime, + pub created_at: DateTime, } #[derive(Associations, Insertable, Identifiable, Debug, Clone, Copy)] diff --git a/crates/crates_io_database/src/models/krate.rs b/crates/crates_io_database/src/models/krate.rs index 0031e958ac6..2799f1df6b7 100644 --- a/crates/crates_io_database/src/models/krate.rs +++ b/crates/crates_io_database/src/models/krate.rs @@ -2,7 +2,7 @@ use crate::models::helpers::with_count::*; use crate::models::version::TopVersions; use crate::models::{CrateOwner, Owner, OwnerKind, ReverseDependency, User, Version}; use crate::schema::*; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use crates_io_diesel_helpers::canon_crate_name; use diesel::associations::Identifiable; use diesel::dsl; @@ -40,8 +40,8 @@ pub struct CrateName { pub struct Crate { pub id: i32, pub name: String, - pub updated_at: NaiveDateTime, - pub created_at: NaiveDateTime, + pub updated_at: DateTime, + pub created_at: DateTime, pub description: Option, pub homepage: Option, pub documentation: Option, diff --git a/crates/crates_io_database/src/models/token.rs b/crates/crates_io_database/src/models/token.rs index 03b8b883cf8..2e6dbfaf2a5 100644 --- a/crates/crates_io_database/src/models/token.rs +++ b/crates/crates_io_database/src/models/token.rs @@ -1,16 +1,16 @@ mod scopes; use bon::Builder; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::dsl::now; use diesel::prelude::*; +use diesel::sql_types::Timestamptz; use diesel_async::scoped_futures::ScopedFutureExt; use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; pub use self::scopes::{CrateScope, EndpointScope}; use crate::models::User; use crate::schema::api_tokens; -use crate::utils::rfc3339; use crate::utils::token::{HashedToken, PlainToken}; #[derive(Debug, Insertable, Builder)] @@ -25,7 +25,7 @@ pub struct NewApiToken { pub crate_scopes: Option>, /// A list of endpoint scopes or `None` for the `legacy` endpoint scope (see RFC #2947) pub endpoint_scopes: Option>, - pub expired_at: Option, + pub expired_at: Option>, } impl NewApiToken { @@ -46,18 +46,15 @@ pub struct ApiToken { #[serde(skip)] pub user_id: i32, pub name: String, - #[serde(with = "rfc3339")] - pub created_at: NaiveDateTime, - #[serde(with = "rfc3339::option")] - pub last_used_at: Option, + pub created_at: DateTime, + pub last_used_at: Option>, #[serde(skip)] pub revoked: bool, /// `None` or a list of crate scope patterns (see RFC #2947) pub crate_scopes: Option>, /// A list of endpoint scopes or `None` for the `legacy` endpoint scope (see RFC #2947) pub endpoint_scopes: Option>, - #[serde(with = "rfc3339::option")] - pub expired_at: Option, + pub expired_at: Option>, } impl ApiToken { @@ -80,7 +77,7 @@ impl ApiToken { .transaction(|conn| { async move { diesel::update(tokens) - .set(api_tokens::last_used_at.eq(now.nullable())) + .set(api_tokens::last_used_at.eq(now.into_sql::().nullable())) .returning(ApiToken::as_returning()) .get_result(conn) .await @@ -111,20 +108,23 @@ mod tests { created_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 11) - .unwrap(), - last_used_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() - .and_hms_opt(14, 23, 12), + .and_utc(), + last_used_at: Some( + NaiveDate::from_ymd_opt(2017, 1, 6) + .unwrap() + .and_hms_opt(14, 23, 12) + .unwrap() + .and_utc(), + ), crate_scopes: None, endpoint_scopes: None, expired_at: None, }; let json = serde_json::to_string(&tok).unwrap(); + assert_some!(json.as_str().find(r#""created_at":"2017-01-06T14:23:11Z""#)); assert_some!(json .as_str() - .find(r#""created_at":"2017-01-06T14:23:11+00:00""#)); - assert_some!(json - .as_str() - .find(r#""last_used_at":"2017-01-06T14:23:12+00:00""#)); + .find(r#""last_used_at":"2017-01-06T14:23:12Z""#)); } } diff --git a/crates/crates_io_database/src/models/user.rs b/crates/crates_io_database/src/models/user.rs index bfb844d6d5d..9d060361ca5 100644 --- a/crates/crates_io_database/src/models/user.rs +++ b/crates/crates_io_database/src/models/user.rs @@ -1,5 +1,5 @@ use bon::Builder; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::dsl::sql; use diesel::prelude::*; use diesel::sql_types::Integer; @@ -22,7 +22,7 @@ pub struct User { pub gh_avatar: Option, pub gh_id: i32, pub account_lock_reason: Option, - pub account_lock_until: Option, + pub account_lock_until: Option>, pub is_admin: bool, pub publish_notifications: bool, } diff --git a/crates/crates_io_database/src/models/version.rs b/crates/crates_io_database/src/models/version.rs index 4baff8d6a8b..1fc2e1eef5d 100644 --- a/crates/crates_io_database/src/models/version.rs +++ b/crates/crates_io_database/src/models/version.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use bon::Builder; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use crates_io_index::features::FeaturesMap; use diesel::prelude::*; use diesel_async::scoped_futures::ScopedFutureExt; @@ -18,8 +18,8 @@ pub struct Version { pub id: i32, pub crate_id: i32, pub num: String, - pub updated_at: NaiveDateTime, - pub created_at: NaiveDateTime, + pub updated_at: DateTime, + pub created_at: DateTime, pub downloads: i32, pub features: serde_json::Value, pub yanked: bool, @@ -84,7 +84,7 @@ pub struct NewVersion<'a> { num: &'a str, #[builder(default = strip_build_metadata(num))] pub num_no_build: &'a str, - created_at: Option<&'a NaiveDateTime>, + created_at: Option<&'a DateTime>, yanked: Option, #[builder(default = serde_json::Value::Object(Default::default()))] features: serde_json::Value, @@ -170,10 +170,10 @@ impl TopVersions { /// highest version (in semver order) for a collection of date/version pairs. pub fn from_date_version_pairs(pairs: T) -> Self where - T: IntoIterator, + T: IntoIterator, String)>, { // filter out versions that we can't parse - let pairs: Vec<(NaiveDateTime, semver::Version)> = pairs + let pairs: Vec<(DateTime, semver::Version)> = pairs .into_iter() .filter_map(|(date, version)| { semver::Version::parse(&version) @@ -201,12 +201,12 @@ impl TopVersions { #[cfg(test)] mod tests { - use super::TopVersions; + use super::*; use chrono::NaiveDateTime; #[track_caller] - fn date(str: &str) -> NaiveDateTime { - str.parse().unwrap() + fn date(str: &str) -> DateTime { + str.parse::().unwrap().and_utc() } #[track_caller] diff --git a/crates/crates_io_database/src/schema.patch b/crates/crates_io_database/src/schema.patch index 1aa7e443619..e46a4bb4429 100644 --- a/crates/crates_io_database/src/schema.patch +++ b/crates/crates_io_database/src/schema.patch @@ -22,11 +22,11 @@ + endpoint_scopes -> Nullable>, /// The `expired_at` column of the `api_tokens` table. /// - /// Its SQL type is `Nullable`. + /// Its SQL type is `Nullable`. @@ -180,12 +178,6 @@ /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, - /// The `path` column of the `categories` table. - /// - /// Its SQL type is `Ltree`. diff --git a/crates/crates_io_database/src/schema.rs b/crates/crates_io_database/src/schema.rs index 6e22797e84a..94f4b0147d4 100644 --- a/crates/crates_io_database/src/schema.rs +++ b/crates/crates_io_database/src/schema.rs @@ -48,16 +48,16 @@ diesel::table! { name -> Varchar, /// The `created_at` column of the `api_tokens` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, /// The `last_used_at` column of the `api_tokens` table. /// - /// Its SQL type is `Nullable`. + /// Its SQL type is `Nullable`. /// /// (Automatically generated by Diesel.) - last_used_at -> Nullable, + last_used_at -> Nullable, /// The `revoked` column of the `api_tokens` table. /// /// Its SQL type is `Bool`. @@ -70,12 +70,12 @@ diesel::table! { endpoint_scopes -> Nullable>, /// The `expired_at` column of the `api_tokens` table. /// - /// Its SQL type is `Nullable`. + /// Its SQL type is `Nullable`. /// /// (Automatically generated by Diesel.) - expired_at -> Nullable, + expired_at -> Nullable, /// timestamp of when the user was informed about their token's impending expiration - expiry_notification_at -> Nullable, + expiry_notification_at -> Nullable, } } @@ -110,16 +110,16 @@ diesel::table! { retries -> Int4, /// The `last_retry` column of the `background_jobs` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - last_retry -> Timestamp, + last_retry -> Timestamptz, /// The `created_at` column of the `background_jobs` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, /// The `priority` column of the `background_jobs` table. /// /// Its SQL type is `Int2`. @@ -169,10 +169,10 @@ diesel::table! { crates_cnt -> Int4, /// The `created_at` column of the `categories` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, } } @@ -211,10 +211,10 @@ diesel::table! { crate_id -> Int4, /// The `created_at` column of the `crate_owner_invitations` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, /// The `token` column of the `crate_owner_invitations` table. /// /// Its SQL type is `Text`. @@ -241,10 +241,10 @@ diesel::table! { owner_id -> Int4, /// The `created_at` column of the `crate_owners` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, /// The `created_by` column of the `crate_owners` table. /// /// Its SQL type is `Nullable`. @@ -259,10 +259,10 @@ diesel::table! { deleted -> Bool, /// The `updated_at` column of the `crate_owners` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - updated_at -> Timestamp, + updated_at -> Timestamptz, /// `owner_kind = 0` refers to `users`, `owner_kind = 1` refers to `teams`. owner_kind -> Int4, /// The `email_notifications` column of the `crate_owners` table. @@ -296,16 +296,16 @@ diesel::table! { name -> Varchar, /// The `updated_at` column of the `crates` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - updated_at -> Timestamp, + updated_at -> Timestamptz, /// The `created_at` column of the `crates` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, /// The `description` column of the `crates` table. /// /// Its SQL type is `Nullable`. @@ -534,10 +534,10 @@ diesel::table! { token -> Text, /// The `token_generated_at` column of the `emails` table. /// - /// Its SQL type is `Nullable`. + /// Its SQL type is `Nullable`. /// /// (Automatically generated by Diesel.) - token_generated_at -> Nullable, + token_generated_at -> Nullable, } } @@ -586,10 +586,10 @@ diesel::table! { crates_cnt -> Int4, /// The `created_at` column of the `keywords` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, } } @@ -636,10 +636,10 @@ diesel::table! { tokens -> Int4, /// The `last_refill` column of the `publish_limit_buckets` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - last_refill -> Timestamp, + last_refill -> Timestamptz, /// The `action` column of the `publish_limit_buckets` table. /// /// Its SQL type is `Int4`. @@ -668,10 +668,10 @@ diesel::table! { burst -> Int4, /// The `expires_at` column of the `publish_rate_overrides` table. /// - /// Its SQL type is `Nullable`. + /// Its SQL type is `Nullable`. /// /// (Automatically generated by Diesel.) - expires_at -> Nullable, + expires_at -> Nullable, /// The `action` column of the `publish_rate_overrides` table. /// /// Its SQL type is `Int4`. @@ -694,10 +694,10 @@ diesel::table! { version_id -> Int4, /// The `rendered_at` column of the `readme_renderings` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - rendered_at -> Timestamp, + rendered_at -> Timestamptz, } } @@ -814,10 +814,10 @@ diesel::table! { account_lock_reason -> Nullable, /// The `account_lock_until` column of the `users` table. /// - /// Its SQL type is `Nullable`. + /// Its SQL type is `Nullable`. /// /// (Automatically generated by Diesel.) - account_lock_until -> Nullable, + account_lock_until -> Nullable, /// The `is_admin` column of the `users` table. /// /// Its SQL type is `Bool`. @@ -904,10 +904,10 @@ diesel::table! { action -> Int4, /// The `time` column of the `version_owner_actions` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - time -> Timestamp, + time -> Timestamptz, } } @@ -936,16 +936,16 @@ diesel::table! { num -> Varchar, /// The `updated_at` column of the `versions` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - updated_at -> Timestamp, + updated_at -> Timestamptz, /// The `created_at` column of the `versions` table. /// - /// Its SQL type is `Timestamp`. + /// Its SQL type is `Timestamptz`. /// /// (Automatically generated by Diesel.) - created_at -> Timestamp, + created_at -> Timestamptz, /// The `downloads` column of the `versions` table. /// /// Its SQL type is `Int4`. diff --git a/crates/crates_io_database/src/utils/mod.rs b/crates/crates_io_database/src/utils/mod.rs index a03b958f225..79c66ba6356 100644 --- a/crates/crates_io_database/src/utils/mod.rs +++ b/crates/crates_io_database/src/utils/mod.rs @@ -1,2 +1 @@ -pub mod rfc3339; pub mod token; diff --git a/crates/crates_io_database/src/utils/rfc3339.rs b/crates/crates_io_database/src/utils/rfc3339.rs deleted file mode 100644 index 22136f7f7a9..00000000000 --- a/crates/crates_io_database/src/utils/rfc3339.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Convenience functions for serializing and deserializing times in RFC 3339 format. -//! Used for returning time values in JSON API responses. -//! Example: `2012-02-22T14:53:18+00:00`. - -use chrono::{DateTime, NaiveDateTime, Utc}; -use serde::{self, Deserialize, Deserializer, Serializer}; - -pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result -where - S: Serializer, -{ - let s = DateTime::::from_naive_utc_and_offset(*dt, Utc).to_rfc3339(); - serializer.serialize_str(&s) -} -pub fn deserialize<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - let dt = DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?; - Ok(dt.naive_utc()) -} - -/// Wrapper for dealing with `Option` -pub mod option { - use chrono::NaiveDateTime; - use serde::{Deserializer, Serializer}; - - pub fn serialize(dt: &Option, serializer: S) -> Result - where - S: Serializer, - { - match *dt { - Some(dt) => super::serialize(&dt, serializer), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - match super::deserialize(deserializer) { - Ok(dt) => Ok(Some(dt)), - Err(_) => Ok(None), - } - } -} diff --git a/crates/crates_io_diesel_helpers/src/fns.rs b/crates/crates_io_diesel_helpers/src/fns.rs index c926a05abf2..ae137d8ecb8 100644 --- a/crates/crates_io_diesel_helpers/src/fns.rs +++ b/crates/crates_io_diesel_helpers/src/fns.rs @@ -1,11 +1,11 @@ use diesel::define_sql_function; -use diesel::sql_types::{Date, Double, Integer, Interval, SingleValue, Text, Timestamp}; +use diesel::sql_types::{Date, Double, Integer, Interval, SingleValue, Text, Timestamptz}; define_sql_function!(#[aggregate] fn array_agg(x: T) -> Array); define_sql_function!(fn canon_crate_name(x: Text) -> Text); define_sql_function!(fn to_char(a: Date, b: Text) -> Text); define_sql_function!(fn lower(x: Text) -> Text); -define_sql_function!(fn date_part(x: Text, y: Timestamp) -> Double); +define_sql_function!(fn date_part(x: Text, y: Timestamptz) -> Double); define_sql_function! { #[sql_name = "date_part"] fn interval_part(x: Text, y: Interval) -> Double; diff --git a/migrations/2025-02-17-133723_timezones/down.sql b/migrations/2025-02-17-133723_timezones/down.sql new file mode 100644 index 00000000000..c3cf8457a3e --- /dev/null +++ b/migrations/2025-02-17-133723_timezones/down.sql @@ -0,0 +1,45 @@ +SET timezone = 'UTC'; + +alter table api_tokens alter created_at type timestamp; +alter table api_tokens alter last_used_at type timestamp; +alter table api_tokens alter expired_at type timestamp; +alter table api_tokens alter expiry_notification_at type timestamp; + +alter table background_jobs alter last_retry type timestamp; +alter table background_jobs alter created_at type timestamp; + +alter table categories alter created_at type timestamp; + +alter table crate_owner_invitations alter created_at type timestamp; + +alter table crate_owners alter created_at type timestamp; +alter table crate_owners alter updated_at type timestamp; + +drop trigger trigger_crates_tsvector_update on crates; + +alter table crates alter created_at type timestamp; +alter table crates alter updated_at type timestamp; + +create trigger trigger_crates_tsvector_update + before insert or update + of updated_at + on crates + for each row +execute procedure trigger_crates_name_search(); + +alter table emails alter token_generated_at type timestamp; + +alter table keywords alter created_at type timestamp; + +alter table publish_limit_buckets alter last_refill type timestamp; + +alter table publish_rate_overrides alter expires_at type timestamp; + +alter table readme_renderings alter rendered_at type timestamp; + +alter table users alter account_lock_until type timestamp; + +alter table version_owner_actions alter time type timestamp; + +alter table versions alter updated_at type timestamp; +alter table versions alter created_at type timestamp; diff --git a/migrations/2025-02-17-133723_timezones/up.sql b/migrations/2025-02-17-133723_timezones/up.sql new file mode 100644 index 00000000000..4d330395d9f --- /dev/null +++ b/migrations/2025-02-17-133723_timezones/up.sql @@ -0,0 +1,45 @@ +SET timezone = 'UTC'; + +alter table api_tokens alter created_at type timestamptz; +alter table api_tokens alter last_used_at type timestamptz; +alter table api_tokens alter expired_at type timestamptz; +alter table api_tokens alter expiry_notification_at type timestamptz; + +alter table background_jobs alter last_retry type timestamptz; +alter table background_jobs alter created_at type timestamptz; + +alter table categories alter created_at type timestamptz; + +alter table crate_owner_invitations alter created_at type timestamptz; + +alter table crate_owners alter created_at type timestamptz; +alter table crate_owners alter updated_at type timestamptz; + +drop trigger trigger_crates_tsvector_update on crates; + +alter table crates alter created_at type timestamptz; +alter table crates alter updated_at type timestamptz; + +create trigger trigger_crates_tsvector_update + before insert or update + of updated_at + on crates + for each row +execute procedure trigger_crates_name_search(); + +alter table emails alter token_generated_at type timestamptz; + +alter table keywords alter created_at type timestamptz; + +alter table publish_limit_buckets alter last_refill type timestamptz; + +alter table publish_rate_overrides alter expires_at type timestamptz; + +alter table readme_renderings alter rendered_at type timestamptz; + +alter table users alter account_lock_until type timestamptz; + +alter table version_owner_actions alter time type timestamptz; + +alter table versions alter updated_at type timestamptz; +alter table versions alter created_at type timestamptz; diff --git a/src/auth.rs b/src/auth.rs index baa6a1c4f27..99eddc496a8 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -264,7 +264,7 @@ fn ensure_not_locked(user: &User) -> AppResult<()> { if let Some(reason) = &user.account_lock_reason { let still_locked = user .account_lock_until - .map(|until| until > Utc::now().naive_utc()) + .map(|until| until > Utc::now()) .unwrap_or(true); if still_locked { diff --git a/src/bin/crates-admin/delete_crate.rs b/src/bin/crates-admin/delete_crate.rs index 971e1a3ea37..306b12ec208 100644 --- a/src/bin/crates-admin/delete_crate.rs +++ b/src/bin/crates-admin/delete_crate.rs @@ -1,6 +1,6 @@ use crate::dialoguer; use anyhow::Context; -use chrono::{NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use colored::Colorize; use crates_io::models::{NewDeletedCrate, User}; use crates_io::schema::{crate_downloads, deleted_crates}; @@ -86,9 +86,8 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> { if let Some(crate_info) = existing_crates.iter().find(|info| info.name == *name) { let id = crate_info.id; - let created_at = crate_info.created_at.and_utc(); let deleted_crate = NewDeletedCrate::builder(name) - .created_at(&created_at) + .created_at(&crate_info.created_at) .deleted_at(&now) .deleted_by(deleted_by.id) .maybe_message(opts.message.as_deref()) @@ -149,7 +148,7 @@ struct CrateInfo { #[diesel(select_expression = crates::columns::id)] id: i32, #[diesel(select_expression = crates::columns::created_at)] - created_at: NaiveDateTime, + created_at: DateTime, #[diesel(select_expression = crate_downloads::columns::downloads)] downloads: i64, #[diesel(select_expression = owners_subquery())] diff --git a/src/bin/monitor.rs b/src/bin/monitor.rs index ffd3e27eea9..78e8ff2a277 100644 --- a/src/bin/monitor.rs +++ b/src/bin/monitor.rs @@ -13,6 +13,7 @@ use crates_io_pagerduty as pagerduty; use crates_io_pagerduty::PagerdutyClient; use crates_io_worker::BackgroundJob; use diesel::prelude::*; +use diesel::sql_types::Timestamptz; use diesel_async::{AsyncPgConnection, RunQueryDsl}; #[tokio::main] @@ -53,7 +54,9 @@ async fn check_failing_background_jobs( let stalled_jobs: Vec = background_jobs::table .select(1.into_sql::()) - .filter(background_jobs::created_at.lt(now - max_job_time.minutes())) + .filter( + background_jobs::created_at.lt(now.into_sql::() - max_job_time.minutes()), + ) .filter(background_jobs::priority.ge(0)) .for_update() .skip_locked() @@ -86,7 +89,7 @@ async fn check_stalled_update_downloads( conn: &mut AsyncPgConnection, pagerduty: &PagerdutyClient, ) -> Result<()> { - use chrono::{DateTime, NaiveDateTime, Utc}; + use chrono::{DateTime, Utc}; const EVENT_KEY: &str = "update_downloads_stalled"; @@ -95,14 +98,13 @@ async fn check_stalled_update_downloads( // Max job execution time in minutes let max_job_time = var_parsed("MONITOR_MAX_UPDATE_DOWNLOADS_TIME")?.unwrap_or(120); - let start_time: Result = background_jobs::table + let start_time: Result, _> = background_jobs::table .filter(background_jobs::job_type.eq(jobs::UpdateDownloads::JOB_NAME)) .select(background_jobs::created_at) .first(conn) .await; if let Ok(start_time) = start_time { - let start_time = DateTime::::from_naive_utc_and_offset(start_time, Utc); let minutes = Utc::now().signed_duration_since(start_time).num_minutes(); if minutes > max_job_time { diff --git a/src/controllers/crate_owner_invitation.rs b/src/controllers/crate_owner_invitation.rs index cade05e51ac..31643129d39 100644 --- a/src/controllers/crate_owner_invitation.rs +++ b/src/controllers/crate_owner_invitation.rs @@ -278,7 +278,7 @@ async fn prepare_list( .ok_or_else(|| internal(format!("missing crate with id {}", invitation.crate_id)))? .clone(), created_at: invitation.created_at, - expires_at: invitation.expires_at.naive_utc(), + expires_at: invitation.expires_at, }); users_in_response.insert(invitation.invited_user_id); users_in_response.insert(invitation.invited_by_user_id); diff --git a/src/controllers/helpers/pagination.rs b/src/controllers/helpers/pagination.rs index 4820d95db21..af04c0486a5 100644 --- a/src/controllers/helpers/pagination.rs +++ b/src/controllers/helpers/pagination.rs @@ -544,6 +544,7 @@ pub(crate) use seek; #[cfg(test)] mod tests { use super::*; + use chrono::Utc; use http::{Method, Request, StatusCode}; use insta::assert_snapshot; @@ -638,7 +639,9 @@ mod tests { } mod seek { - use chrono::naive::serde::ts_microseconds; + use chrono::serde::ts_microseconds; + use chrono::Utc; + seek!( pub(super) enum Seek { Id { @@ -646,7 +649,7 @@ mod tests { }, New { #[serde(with = "ts_microseconds")] - dt: chrono::NaiveDateTime, + dt: chrono::DateTime, id: i32, }, RecentDownloads { @@ -659,8 +662,8 @@ mod tests { #[test] fn test_seek_macro_encode_and_decode() { - use chrono::naive::serde::ts_microseconds; - use chrono::{NaiveDate, NaiveDateTime}; + use chrono::serde::ts_microseconds; + use chrono::NaiveDate; use seek::*; let assert_decode_after = |seek: Seek, query: &str, expect| { @@ -678,10 +681,11 @@ mod tests { let query = format!("seek={}", encode_seek(&payload).unwrap()); assert_decode_after(seek, &query, Some(payload)); - let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8) + let dt = NaiveDate::from_ymd_opt(2016, 7, 8) .unwrap() .and_hms_opt(9, 10, 11) - .unwrap(); + .unwrap() + .and_utc(); let seek = Seek::New; let payload = SeekPayload::New(New { dt, id }); let query = format!("seek={}", encode_seek(&payload).unwrap()); @@ -711,7 +715,7 @@ mod tests { // Ensures it still encodes compactly with a field struct #[derive(Debug, Default, Serialize, PartialEq)] struct NewTuple( - #[serde(with = "ts_microseconds")] chrono::NaiveDateTime, + #[serde(with = "ts_microseconds")] chrono::DateTime, i32, ); assert_eq!( @@ -722,15 +726,16 @@ mod tests { #[test] fn test_seek_macro_conv() { - use chrono::{NaiveDate, NaiveDateTime}; + use chrono::NaiveDate; use seek::*; let id = 1234; assert_eq!(Seek::from(SeekPayload::Id(Id { id })), Seek::Id); - let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8) + let dt = NaiveDate::from_ymd_opt(2016, 7, 8) .unwrap() .and_hms_opt(9, 10, 11) - .unwrap(); + .unwrap() + .and_utc(); assert_eq!(Seek::from(SeekPayload::New(New { dt, id })), Seek::New); let downloads = None; diff --git a/src/controllers/krate/delete.rs b/src/controllers/krate/delete.rs index 56297279117..66a4bba04c2 100644 --- a/src/controllers/krate/delete.rs +++ b/src/controllers/krate/delete.rs @@ -81,7 +81,7 @@ pub async fn delete_crate( } } - let created_at = krate.created_at.and_utc(); + let created_at = krate.created_at; let age = Utc::now().signed_duration_since(created_at); if age > TimeDelta::hours(72) { diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index aa35c6df530..fd124c9245e 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -13,6 +13,7 @@ use crates_io_tarball::{process_tarball, TarballError}; use crates_io_worker::{BackgroundJob, EnqueueError}; use diesel::dsl::{exists, select}; use diesel::prelude::*; +use diesel::sql_types::Timestamptz; use diesel_async::scoped_futures::ScopedFutureExt; use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; use futures_util::TryFutureExt; @@ -586,7 +587,7 @@ async fn count_versions_published_today( versions::table .filter(versions::crate_id.eq(crate_id)) - .filter(versions::created_at.gt(now - 24.hours())) + .filter(versions::created_at.gt(now.into_sql::() - 24.hours())) .count() .get_result(conn) .await diff --git a/src/controllers/krate/search.rs b/src/controllers/krate/search.rs index d5a335d9faf..dd3051a3b03 100644 --- a/src/controllers/krate/search.rs +++ b/src/controllers/krate/search.rs @@ -632,7 +632,8 @@ impl FilterParams { mod seek { use super::Record; use crate::controllers::helpers::pagination::seek; - use chrono::naive::serde::ts_microseconds; + use chrono::serde::ts_microseconds; + use chrono::Utc; seek!( pub enum Seek { @@ -641,12 +642,12 @@ mod seek { }, New { #[serde(with = "ts_microseconds")] - created_at: chrono::NaiveDateTime, + created_at: chrono::DateTime, id: i32, }, RecentUpdates { #[serde(with = "ts_microseconds")] - updated_at: chrono::NaiveDateTime, + updated_at: chrono::DateTime, id: i32, }, RecentDownloads { diff --git a/src/controllers/krate/versions.rs b/src/controllers/krate/versions.rs index 8f763efdac4..d17a4b03ae3 100644 --- a/src/controllers/krate/versions.rs +++ b/src/controllers/krate/versions.rs @@ -374,7 +374,8 @@ async fn list_by_semver( mod seek { use crate::controllers::helpers::pagination::seek; use crate::models::{User, Version}; - use chrono::naive::serde::ts_microseconds; + use chrono::serde::ts_microseconds; + use chrono::Utc; // We might consider refactoring this to use named fields, which would be clearer and more // flexible. It's also worth noting that we currently encode seek compactly as a Vec, which @@ -386,7 +387,7 @@ mod seek { }, Date { #[serde(with = "ts_microseconds")] - created_at: chrono::NaiveDateTime, + created_at: chrono::DateTime, id: i32, }, } diff --git a/src/controllers/token.rs b/src/controllers/token.rs index 83d80b20bd8..61c402da799 100644 --- a/src/controllers/token.rs +++ b/src/controllers/token.rs @@ -1,6 +1,5 @@ use crate::models::ApiToken; use crate::schema::api_tokens; -use crate::util::rfc3339; use crate::views::EncodableApiTokenWithToken; use crate::app::AppState; @@ -13,10 +12,11 @@ use axum::response::{IntoResponse, Response}; use axum::Json; use axum_extra::json; use axum_extra::response::ErasedJson; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::data_types::PgInterval; use diesel::dsl::{now, IntervalDsl}; use diesel::prelude::*; +use diesel::sql_types::Timestamptz; use diesel_async::RunQueryDsl; use http::request::Parts; use http::StatusCode; @@ -60,7 +60,7 @@ pub async fn list_api_tokens( .filter( api_tokens::expired_at.is_null().or(api_tokens::expired_at .assume_not_null() - .gt(now - params.expired_days_interval())), + .gt(now.into_sql::() - params.expired_days_interval())), ) .order(api_tokens::id.desc()) .load(&mut conn) @@ -75,8 +75,7 @@ pub struct NewApiToken { name: String, crate_scopes: Option>, endpoint_scopes: Option>, - #[serde(default, with = "rfc3339::option")] - expired_at: Option, + expired_at: Option>, } /// The incoming serialization format for the `ApiToken` model. diff --git a/src/index.rs b/src/index.rs index 5b43c7e956a..0e52eb91962 100644 --- a/src/index.rs +++ b/src/index.rs @@ -162,15 +162,8 @@ mod tests { .await .unwrap(); - let created_at_1 = Utc::now() - .checked_sub_days(Days::new(14)) - .unwrap() - .naive_utc(); - - let created_at_2 = Utc::now() - .checked_sub_days(Days::new(7)) - .unwrap() - .naive_utc(); + let created_at_1 = Utc::now().checked_sub_days(Days::new(14)).unwrap(); + let created_at_2 = Utc::now().checked_sub_days(Days::new(7)).unwrap(); let fooo = CrateBuilder::new("foo", user_id) .version(VersionBuilder::new("0.1.0")) diff --git a/src/rate_limiter.rs b/src/rate_limiter.rs index 5a2166be9ea..5416a9ffaa4 100644 --- a/src/rate_limiter.rs +++ b/src/rate_limiter.rs @@ -1,6 +1,6 @@ use crate::schema::{publish_limit_buckets, publish_rate_overrides}; use crate::util::errors::{AppResult, TooManyRequests}; -use chrono::{NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use crates_io_diesel_helpers::{date_part, floor, greatest, interval_part, least, pg_enum}; use diesel::dsl::IntervalDsl; use diesel::prelude::*; @@ -81,7 +81,7 @@ impl RateLimiter { conn: &mut AsyncPgConnection, ) -> AppResult<()> { let bucket = self - .take_token(uploader, performed_action, Utc::now().naive_utc(), conn) + .take_token(uploader, performed_action, Utc::now(), conn) .await?; if bucket.tokens >= 1 { Ok(()) @@ -107,7 +107,7 @@ impl RateLimiter { &self, uploader: i32, performed_action: LimitedAction, - now: NaiveDateTime, + now: DateTime, conn: &mut AsyncPgConnection, ) -> QueryResult { let config = self.config_for_action(performed_action); @@ -176,13 +176,14 @@ impl RateLimiter { struct Bucket { user_id: i32, tokens: i32, - last_refill: NaiveDateTime, + last_refill: DateTime, action: LimitedAction, } #[cfg(test)] mod tests { use super::*; + use chrono::NaiveDateTime; use crates_io_test_db::TestDatabase; #[tokio::test] @@ -371,7 +372,8 @@ mod tests { let mut conn = test_db.async_connect().await; // Subsecond rates have floating point rounding issues, so use a known // timestamp that rounds fine - let now = NaiveDateTime::parse_from_str("2019-03-19T21:11:24.620401", "%FT%T%.f")?; + let now = + NaiveDateTime::parse_from_str("2019-03-19T21:11:24.620401", "%FT%T%.f")?.and_utc(); let rate = SampleRateLimiter { rate: Duration::from_millis(100), @@ -714,7 +716,7 @@ mod tests { async fn new_user_bucket( conn: &mut AsyncPgConnection, tokens: i32, - now: NaiveDateTime, + now: DateTime, ) -> QueryResult { diesel::insert_into(publish_limit_buckets::table) .values(Bucket { @@ -751,9 +753,9 @@ mod tests { /// precision, but some platforms (notably Linux) provide nanosecond /// precision, meaning that round tripping through the database would /// change the value. - fn now() -> NaiveDateTime { + fn now() -> DateTime { let now = Utc::now(); let nanos = now.timestamp_subsec_nanos(); - now.naive_utc() - chrono::Duration::nanoseconds(nanos.into()) + now - chrono::Duration::nanoseconds(nanos.into()) } } diff --git a/src/tests/account_lock.rs b/src/tests/account_lock.rs index c40606a5913..20ec13125ea 100644 --- a/src/tests/account_lock.rs +++ b/src/tests/account_lock.rs @@ -1,12 +1,12 @@ use crate::tests::{util::RequestHelper, TestApp}; -use chrono::{DateTime, Duration, NaiveDateTime, Utc}; +use chrono::{DateTime, Duration, Utc}; use http::StatusCode; use insta::assert_snapshot; const URL: &str = "/api/v1/me"; const LOCK_REASON: &str = "test lock reason"; -async fn lock_account(app: &TestApp, user_id: i32, until: Option) { +async fn lock_account(app: &TestApp, user_id: i32, until: Option>) { use crate::schema::users; use diesel::prelude::*; use diesel_async::RunQueryDsl; @@ -36,10 +36,7 @@ async fn account_locked_indefinitely() { #[tokio::test(flavor = "multi_thread")] async fn account_locked_with_future_expiry() { - let until = "2099-12-12T12:12:12Z" - .parse::>() - .unwrap() - .naive_utc(); + let until = "2099-12-12T12:12:12Z".parse::>().unwrap(); let (app, _anon, user) = TestApp::init().with_user().await; lock_account(&app, user.as_model().id, Some(until)).await; @@ -51,7 +48,7 @@ async fn account_locked_with_future_expiry() { #[tokio::test(flavor = "multi_thread")] async fn expired_account_lock() { - let until = Utc::now().naive_utc() - Duration::days(1); + let until = Utc::now() - Duration::days(1); let (app, _anon, user) = TestApp::init().with_user().await; lock_account(&app, user.as_model().id, Some(until)).await; diff --git a/src/tests/builders/krate.rs b/src/tests/builders/krate.rs index 09388e8f4ab..3a289e6ae52 100644 --- a/src/tests/builders/krate.rs +++ b/src/tests/builders/krate.rs @@ -3,7 +3,7 @@ use crate::schema::{crate_downloads, crates, version_downloads}; use crate::util::errors::AppResult; use super::VersionBuilder; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; @@ -17,7 +17,7 @@ pub struct CrateBuilder<'a> { krate: NewCrate<'a>, owner_id: i32, recent_downloads: Option, - updated_at: Option, + updated_at: Option>, versions: Vec, } @@ -104,7 +104,7 @@ impl<'a> CrateBuilder<'a> { } /// Sets the crate's `updated_at` value. - pub fn updated_at(mut self, updated_at: NaiveDateTime) -> Self { + pub fn updated_at(mut self, updated_at: DateTime) -> Self { self.updated_at = Some(updated_at); self } diff --git a/src/tests/builders/version.rs b/src/tests/builders/version.rs index 987029da34c..d8c0ccb1ac5 100644 --- a/src/tests/builders/version.rs +++ b/src/tests/builders/version.rs @@ -3,13 +3,13 @@ use crate::schema::dependencies; use crate::util::errors::AppResult; use std::collections::BTreeMap; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; /// A builder to create version records for the purpose of inserting directly into the database. pub struct VersionBuilder { - created_at: Option, + created_at: Option>, dependencies: Vec<(i32, Option<&'static str>)>, features: BTreeMap>, license: Option, @@ -49,7 +49,7 @@ impl VersionBuilder { } /// Sets the version's `created_at` value. - pub fn created_at(mut self, created_at: NaiveDateTime) -> Self { + pub fn created_at(mut self, created_at: DateTime) -> Self { self.created_at = Some(created_at); self } diff --git a/src/tests/krate/publish/rate_limit.rs b/src/tests/krate/publish/rate_limit.rs index 2adffa18cde..d9959bba7cc 100644 --- a/src/tests/krate/publish/rate_limit.rs +++ b/src/tests/krate/publish/rate_limit.rs @@ -2,7 +2,7 @@ use crate::rate_limiter::LimitedAction; use crate::schema::{publish_limit_buckets, publish_rate_overrides}; use crate::tests::builders::PublishBuilder; use crate::tests::util::{RequestHelper, TestApp}; -use chrono::{NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use diesel::ExpressionMethods; use diesel_async::RunQueryDsl; use http::StatusCode; @@ -104,7 +104,7 @@ async fn publish_new_crate_override_loosens_ratelimit() { .values(( publish_rate_overrides::user_id.eq(token.as_model().user_id), publish_rate_overrides::burst.eq(2), - publish_rate_overrides::expires_at.eq(None::), + publish_rate_overrides::expires_at.eq(None::>), publish_rate_overrides::action.eq(LimitedAction::PublishNew), )) .execute(&mut conn) diff --git a/src/tests/routes/crates/list.rs b/src/tests/routes/crates/list.rs index dfa4575ca2f..639d6dc15b6 100644 --- a/src/tests/routes/crates/list.rs +++ b/src/tests/routes/crates/list.rs @@ -4,6 +4,7 @@ use crate::tests::builders::{CrateBuilder, VersionBuilder}; use crate::tests::util::{RequestHelper, TestApp}; use crate::tests::{new_category, new_user}; use crates_io_database::schema::categories; +use diesel::sql_types::Timestamptz; use diesel::{dsl::*, prelude::*, update}; use diesel_async::RunQueryDsl; use googletest::prelude::*; @@ -317,25 +318,25 @@ async fn index_sorting() -> anyhow::Result<()> { // Set the created at column for each crate update(&krate1) - .set(crates::created_at.eq(now - 4.weeks())) + .set(crates::created_at.eq(now.into_sql::() - 4.weeks())) .execute(&mut conn) .await?; update(&krate2) - .set(crates::created_at.eq(now - 1.weeks())) + .set(crates::created_at.eq(now.into_sql::() - 1.weeks())) .execute(&mut conn) .await?; update(crates::table.filter(crates::id.eq_any(vec![krate3.id, krate4.id]))) - .set(crates::created_at.eq(now - 3.weeks())) + .set(crates::created_at.eq(now.into_sql::() - 3.weeks())) .execute(&mut conn) .await?; // Set the updated at column for each crate update(&krate1) - .set(crates::updated_at.eq(now - 3.weeks())) + .set(crates::updated_at.eq(now.into_sql::() - 3.weeks())) .execute(&mut conn) .await?; update(crates::table.filter(crates::id.eq_any(vec![krate2.id, krate3.id]))) - .set(crates::updated_at.eq(now - 5.days())) + .set(crates::updated_at.eq(now.into_sql::() - 5.days())) .execute(&mut conn) .await?; update(&krate4) @@ -533,33 +534,33 @@ async fn ignore_exact_match_on_queries_with_sort() -> anyhow::Result<()> { // Set the created at column for each crate update(&krate1) - .set(crates::created_at.eq(now - 4.weeks())) + .set(crates::created_at.eq(now.into_sql::() - 4.weeks())) .execute(&mut conn) .await?; update(&krate2) - .set(crates::created_at.eq(now - 1.weeks())) + .set(crates::created_at.eq(now.into_sql::() - 1.weeks())) .execute(&mut conn) .await?; update(&krate3) - .set(crates::created_at.eq(now - 2.weeks())) + .set(crates::created_at.eq(now.into_sql::() - 2.weeks())) .execute(&mut conn) .await?; update(&krate4) - .set(crates::created_at.eq(now - 3.weeks())) + .set(crates::created_at.eq(now.into_sql::() - 3.weeks())) .execute(&mut conn) .await?; // Set the updated at column for each crate update(&krate1) - .set(crates::updated_at.eq(now - 3.weeks())) + .set(crates::updated_at.eq(now.into_sql::() - 3.weeks())) .execute(&mut conn) .await?; update(&krate2) - .set(crates::updated_at.eq(now - 5.days())) + .set(crates::updated_at.eq(now.into_sql::() - 5.days())) .execute(&mut conn) .await?; update(&krate3) - .set(crates::updated_at.eq(now - 10.seconds())) + .set(crates::updated_at.eq(now.into_sql::() - 10.seconds())) .execute(&mut conn) .await?; update(&krate4) diff --git a/src/tests/routes/crates/versions/yank_unyank.rs b/src/tests/routes/crates/versions/yank_unyank.rs index 25474a81ea2..233cd5a08dc 100644 --- a/src/tests/routes/crates/versions/yank_unyank.rs +++ b/src/tests/routes/crates/versions/yank_unyank.rs @@ -210,7 +210,7 @@ mod auth { let (app, _, client) = prepare().await; let client = client - .db_new_scoped_token("test-token", None, None, Some(expired_at.naive_utc())) + .db_new_scoped_token("test-token", None, None, Some(expired_at)) .await; let response = client.yank(CRATE_NAME, CRATE_VERSION).await; @@ -230,7 +230,7 @@ mod auth { let (app, _, client) = prepare().await; let client = client - .db_new_scoped_token("test-token", None, None, Some(expired_at.naive_utc())) + .db_new_scoped_token("test-token", None, None, Some(expired_at)) .await; let response = client.yank(CRATE_NAME, CRATE_VERSION).await; diff --git a/src/tests/routes/me/tokens/get.rs b/src/tests/routes/me/tokens/get.rs index 110fcc02c61..2d1bd3cf089 100644 --- a/src/tests/routes/me/tokens/get.rs +++ b/src/tests/routes/me/tokens/get.rs @@ -41,7 +41,7 @@ async fn show_token_with_scopes() { CrateScope::try_from("serde-*").unwrap(), ]) .endpoint_scopes(vec![EndpointScope::PublishUpdate]) - .expired_at((Utc::now() - Duration::days(31)).naive_utc()) + .expired_at(Utc::now() - Duration::days(31)) .build(); let token = assert_ok!(new_token.insert(&mut conn).await); diff --git a/src/tests/routes/me/tokens/list.rs b/src/tests/routes/me/tokens/list.rs index 56f46e4cb2f..06a0d0be9e6 100644 --- a/src/tests/routes/me/tokens/list.rs +++ b/src/tests/routes/me/tokens/list.rs @@ -49,7 +49,7 @@ async fn list_tokens() { let new_token = NewApiToken::builder() .name("qux") .user_id(id) - .expired_at((Utc::now() - Duration::days(1)).naive_utc()) + .expired_at(Utc::now() - Duration::days(1)) .build(); assert_ok!(new_token.insert(&mut conn).await); @@ -84,14 +84,14 @@ async fn list_recently_expired_tokens() { CrateScope::try_from("serde-*").unwrap(), ]) .endpoint_scopes(vec![EndpointScope::PublishUpdate]) - .expired_at((Utc::now() - Duration::days(31)).naive_utc()) + .expired_at(Utc::now() - Duration::days(31)) .build(); assert_ok!(new_token.insert(&mut conn).await); let new_token = NewApiToken::builder() .name("recent") .user_id(id) - .expired_at((Utc::now() - Duration::days(1)).naive_utc()) + .expired_at(Utc::now() - Duration::days(1)) .build(); assert_ok!(new_token.insert(&mut conn).await); diff --git a/src/tests/routes/me/tokens/snapshots/crates_io__tests__routes__me__tokens__create__create_token_with_expiry_date.snap b/src/tests/routes/me/tokens/snapshots/crates_io__tests__routes__me__tokens__create__create_token_with_expiry_date.snap index 6e3e54fc47b..dc8ec84abad 100644 --- a/src/tests/routes/me/tokens/snapshots/crates_io__tests__routes__me__tokens__create__create_token_with_expiry_date.snap +++ b/src/tests/routes/me/tokens/snapshots/crates_io__tests__routes__me__tokens__create__create_token_with_expiry_date.snap @@ -7,7 +7,7 @@ expression: response.json() "crate_scopes": null, "created_at": "[datetime]", "endpoint_scopes": null, - "expired_at": "2024-12-24T07:34:56+00:00", + "expired_at": "2024-12-24T07:34:56Z", "id": "[id]", "last_used_at": "[datetime]", "name": "bar", diff --git a/src/tests/routes/summary.rs b/src/tests/routes/summary.rs index 3a21f2d5c7c..ed6efdd7752 100644 --- a/src/tests/routes/summary.rs +++ b/src/tests/routes/summary.rs @@ -35,7 +35,7 @@ async fn summary_new_crates() { conn.transaction(|conn| { async move { - let now_ = Utc::now().naive_utc(); + let now_ = Utc::now(); let now_plus_two = now_ + chrono::Duration::seconds(2); insert_into(categories::table) diff --git a/src/tests/user.rs b/src/tests/user.rs index a104fcfe30c..11a3d3448dc 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -4,6 +4,7 @@ use crate::tests::util::github::next_gh_id; use crate::tests::util::{MockCookieUser, RequestHelper}; use crate::tests::TestApp; use crate::util::token::HashedToken; +use chrono::{DateTime, Utc}; use crates_io_github::GithubUser; use diesel::prelude::*; use diesel_async::RunQueryDsl; @@ -227,7 +228,6 @@ async fn test_confirm_user_email() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn test_existing_user_email() -> anyhow::Result<()> { use crate::schema::emails; - use chrono::NaiveDateTime; use diesel::update; let (app, _) = TestApp::init().empty().await; @@ -253,7 +253,7 @@ async fn test_existing_user_email() -> anyhow::Result<()> { update(Email::belonging_to(&u)) // Users created before we added verification will have // `NULL` in the `token_generated_at` column. - .set(emails::token_generated_at.eq(None::)) + .set(emails::token_generated_at.eq(None::>)) .execute(&mut conn) .await?; let user = MockCookieUser::new(&app, u); diff --git a/src/tests/util.rs b/src/tests/util.rs index dc7053c8d61..e37d30c571f 100644 --- a/src/tests/util.rs +++ b/src/tests/util.rs @@ -32,7 +32,7 @@ use crate::models::token::{CrateScope, EndpointScope, NewApiToken}; use crate::util::token::PlainToken; use axum::body::{Body, Bytes}; use axum::extract::connect_info::MockConnectInfo; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use cookie::Cookie; use futures_util::FutureExt; use http::header; @@ -316,7 +316,7 @@ impl MockCookieUser { name: &str, crate_scopes: Option>, endpoint_scopes: Option>, - expired_at: Option, + expired_at: Option>, ) -> MockTokenUser { let mut conn = self.app().db_conn().await; diff --git a/src/util.rs b/src/util.rs index 7a8ae22bf73..66d93e79bf5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,6 @@ pub use self::io_util::{read_fill, read_le_u32}; pub use self::request_helpers::*; -pub use crates_io_database::utils::{rfc3339, token}; +pub use crates_io_database::utils::token; pub mod diesel; pub mod errors; diff --git a/src/util/errors.rs b/src/util/errors.rs index 08e6504a957..e2e6312442b 100644 --- a/src/util/errors.rs +++ b/src/util/errors.rs @@ -20,7 +20,7 @@ use std::error::Error; use std::fmt; use axum::Extension; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use diesel::result::{DatabaseErrorKind, Error as DieselError}; use http::StatusCode; use tokio::task::JoinError; @@ -47,7 +47,7 @@ pub fn bad_request(error: S) -> BoxedAppError { custom(StatusCode::BAD_REQUEST, error.to_string()) } -pub fn account_locked(reason: &str, until: Option) -> BoxedAppError { +pub fn account_locked(reason: &str, until: Option>) -> BoxedAppError { let detail = until .map(|until| until.format("%F at %T UTC")) .map(|until| format!("This account is locked until {until}. Reason: {reason}")) diff --git a/src/util/errors/json.rs b/src/util/errors/json.rs index 0760a82fc5f..b2a6a6e6ffa 100644 --- a/src/util/errors/json.rs +++ b/src/util/errors/json.rs @@ -8,7 +8,7 @@ use super::{AppError, BoxedAppError}; use crate::middleware::log_request::CauseField; use crate::rate_limiter::LimitedAction; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use http::{header, StatusCode}; /// Generates a response with the provided status and description as JSON @@ -47,7 +47,7 @@ impl AppError for CustomApiError { #[derive(Debug)] pub(crate) struct TooManyRequests { pub action: LimitedAction, - pub retry_after: NaiveDateTime, + pub retry_after: DateTime, } impl AppError for TooManyRequests { diff --git a/src/views.rs b/src/views.rs index b7503739db0..cf3b5838a32 100644 --- a/src/views.rs +++ b/src/views.rs @@ -1,11 +1,10 @@ -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use crate::external_urls::remove_blocked_urls; use crate::models::{ ApiToken, Category, Crate, CrateOwnerInvitation, Dependency, DependencyKind, Keyword, Owner, ReverseDependency, Team, TopVersions, User, Version, VersionDownload, VersionOwnerAction, }; -use crate::util::rfc3339; use crates_io_github as github; pub mod krate_publish; @@ -17,8 +16,7 @@ pub struct EncodableCategory { pub category: String, pub slug: String, pub description: String, - #[serde(with = "rfc3339")] - pub created_at: NaiveDateTime, + pub created_at: DateTime, pub crates_cnt: i32, } @@ -49,8 +47,7 @@ pub struct EncodableCategoryWithSubcategories { pub category: String, pub slug: String, pub description: String, - #[serde(with = "rfc3339")] - pub created_at: NaiveDateTime, + pub created_at: DateTime, pub crates_cnt: i32, pub subcategories: Vec, pub parent_categories: Vec, @@ -64,10 +61,8 @@ pub struct EncodableCrateOwnerInvitationV1 { pub invited_by_username: String, pub crate_name: String, pub crate_id: i32, - #[serde(with = "rfc3339")] - pub created_at: NaiveDateTime, - #[serde(with = "rfc3339")] - pub expires_at: NaiveDateTime, + pub created_at: DateTime, + pub expires_at: DateTime, } impl EncodableCrateOwnerInvitationV1 { @@ -75,7 +70,7 @@ impl EncodableCrateOwnerInvitationV1 { invitation: CrateOwnerInvitation, inviter_name: String, crate_name: String, - expires_at: NaiveDateTime, + expires_at: DateTime, ) -> Self { Self { invitee_id: invitation.invited_user_id, @@ -95,10 +90,8 @@ pub struct EncodableCrateOwnerInvitation { pub inviter_id: i32, pub crate_id: i32, pub crate_name: String, - #[serde(with = "rfc3339")] - pub created_at: NaiveDateTime, - #[serde(with = "rfc3339")] - pub expires_at: NaiveDateTime, + pub created_at: DateTime, + pub expires_at: DateTime, } #[derive(Deserialize, Serialize, Debug, Copy, Clone)] @@ -169,8 +162,7 @@ impl From for EncodableVersionDownload { pub struct EncodableKeyword { pub id: String, pub keyword: String, - #[serde(with = "rfc3339")] - pub created_at: NaiveDateTime, + pub created_at: DateTime, pub crates_cnt: i32, } @@ -195,14 +187,12 @@ impl From for EncodableKeyword { pub struct EncodableCrate { pub id: String, pub name: String, - #[serde(with = "rfc3339")] - pub updated_at: NaiveDateTime, + pub updated_at: DateTime, pub versions: Option>, pub keywords: Option>, pub categories: Option>, pub badges: [(); 0], - #[serde(with = "rfc3339")] - pub created_at: NaiveDateTime, + pub created_at: DateTime, // NOTE: Used by shields.io, altering `downloads` requires a PR with shields.io pub downloads: i64, pub recent_downloads: Option, @@ -549,8 +539,7 @@ impl From for EncodablePublicUser { pub struct EncodableAuditAction { pub action: String, pub user: EncodablePublicUser, - #[serde(with = "rfc3339")] - pub time: NaiveDateTime, + pub time: DateTime, } #[derive(Serialize, Deserialize, Debug)] @@ -561,10 +550,8 @@ pub struct EncodableVersion { pub num: String, pub dl_path: String, pub readme_path: String, - #[serde(with = "rfc3339")] - pub updated_at: NaiveDateTime, - #[serde(with = "rfc3339")] - pub created_at: NaiveDateTime, + pub updated_at: DateTime, + pub created_at: DateTime, // NOTE: Used by shields.io, altering `downloads` requires a PR with shields.io pub downloads: i32, pub features: serde_json::Value, @@ -700,12 +687,11 @@ mod tests { created_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 11) - .unwrap(), + .unwrap() + .and_utc(), }; let json = serde_json::to_string(&cat).unwrap(); - assert_some!(json - .as_str() - .find(r#""created_at":"2017-01-06T14:23:11+00:00""#)); + assert_some!(json.as_str().find(r#""created_at":"2017-01-06T14:23:11Z""#)); } #[test] @@ -719,14 +705,13 @@ mod tests { created_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 11) - .unwrap(), + .unwrap() + .and_utc(), subcategories: vec![], parent_categories: vec![], }; let json = serde_json::to_string(&cat).unwrap(); - assert_some!(json - .as_str() - .find(r#""created_at":"2017-01-06T14:23:11+00:00""#)); + assert_some!(json.as_str().find(r#""created_at":"2017-01-06T14:23:11Z""#)); } #[test] @@ -737,13 +722,12 @@ mod tests { created_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 11) - .unwrap(), + .unwrap() + .and_utc(), crates_cnt: 0, }; let json = serde_json::to_string(&key).unwrap(); - assert_some!(json - .as_str() - .find(r#""created_at":"2017-01-06T14:23:11+00:00""#)); + assert_some!(json.as_str().find(r#""created_at":"2017-01-06T14:23:11Z""#)); } #[test] @@ -757,11 +741,13 @@ mod tests { updated_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 11) - .unwrap(), + .unwrap() + .and_utc(), created_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 12) - .unwrap(), + .unwrap() + .and_utc(), downloads: 0, features: serde_json::from_str("{}").unwrap(), yanked: false, @@ -796,17 +782,14 @@ mod tests { time: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 12) - .unwrap(), + .unwrap() + .and_utc(), }], }; let json = serde_json::to_string(&ver).unwrap(); - assert_some!(json - .as_str() - .find(r#""updated_at":"2017-01-06T14:23:11+00:00""#)); - assert_some!(json - .as_str() - .find(r#""created_at":"2017-01-06T14:23:12+00:00""#)); - assert_some!(json.as_str().find(r#""time":"2017-01-06T14:23:12+00:00""#)); + assert_some!(json.as_str().find(r#""updated_at":"2017-01-06T14:23:11Z""#)); + assert_some!(json.as_str().find(r#""created_at":"2017-01-06T14:23:12Z""#)); + assert_some!(json.as_str().find(r#""time":"2017-01-06T14:23:12Z""#)); } #[test] @@ -817,7 +800,8 @@ mod tests { updated_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 11) - .unwrap(), + .unwrap() + .and_utc(), versions: None, keywords: None, categories: None, @@ -825,7 +809,8 @@ mod tests { created_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 12) - .unwrap(), + .unwrap() + .and_utc(), downloads: 0, recent_downloads: None, default_version: None, @@ -849,12 +834,8 @@ mod tests { exact_match: false, }; let json = serde_json::to_string(&crt).unwrap(); - assert_some!(json - .as_str() - .find(r#""updated_at":"2017-01-06T14:23:11+00:00""#)); - assert_some!(json - .as_str() - .find(r#""created_at":"2017-01-06T14:23:12+00:00""#)); + assert_some!(json.as_str().find(r#""updated_at":"2017-01-06T14:23:11Z""#)); + assert_some!(json.as_str().find(r#""created_at":"2017-01-06T14:23:12Z""#)); } #[test] @@ -868,18 +849,16 @@ mod tests { created_at: NaiveDate::from_ymd_opt(2017, 1, 6) .unwrap() .and_hms_opt(14, 23, 11) - .unwrap(), + .unwrap() + .and_utc(), expires_at: NaiveDate::from_ymd_opt(2020, 10, 24) .unwrap() .and_hms_opt(16, 30, 00) - .unwrap(), + .unwrap() + .and_utc(), }; let json = serde_json::to_string(&inv).unwrap(); - assert_some!(json - .as_str() - .find(r#""created_at":"2017-01-06T14:23:11+00:00""#)); - assert_some!(json - .as_str() - .find(r#""expires_at":"2020-10-24T16:30:00+00:00""#)); + assert_some!(json.as_str().find(r#""created_at":"2017-01-06T14:23:11Z""#)); + assert_some!(json.as_str().find(r#""expires_at":"2020-10-24T16:30:00Z""#)); } } diff --git a/src/worker/jobs/downloads/update_metadata.rs b/src/worker/jobs/downloads/update_metadata.rs index 664c3412315..a2d676c594c 100644 --- a/src/worker/jobs/downloads/update_metadata.rs +++ b/src/worker/jobs/downloads/update_metadata.rs @@ -108,6 +108,7 @@ mod tests { use crate::models::{Crate, NewCrate, NewUser, NewVersion, User, Version}; use crate::schema::{crate_downloads, crates, versions}; use crates_io_test_db::TestDatabase; + use diesel::sql_types::Timestamptz; use diesel_async::AsyncConnection; async fn user(conn: &mut AsyncPgConnection) -> User { @@ -257,12 +258,12 @@ mod tests { let user = user(&mut conn).await; let (krate, version) = crate_and_version(&mut conn, user.id).await; update(versions::table) - .set(versions::updated_at.eq(now - 2.hours())) + .set(versions::updated_at.eq(now.into_sql::() - 2.hours())) .execute(&mut conn) .await .unwrap(); update(crates::table) - .set(crates::updated_at.eq(now - 2.hours())) + .set(crates::updated_at.eq(now.into_sql::() - 2.hours())) .execute(&mut conn) .await .unwrap(); @@ -351,12 +352,12 @@ mod tests { conn.begin_test_transaction().await.unwrap(); update(versions::table) - .set(versions::updated_at.eq(now - 2.days())) + .set(versions::updated_at.eq(now.into_sql::() - 2.days())) .execute(&mut conn) .await .unwrap(); update(crates::table) - .set(crates::updated_at.eq(now - 2.days())) + .set(crates::updated_at.eq(now.into_sql::() - 2.days())) .execute(&mut conn) .await .unwrap(); @@ -375,11 +376,11 @@ mod tests { super::update(&mut conn).await.unwrap(); let versions_changed = versions::table - .select(versions::updated_at.ne(now - 2.days())) + .select(versions::updated_at.ne(now.into_sql::() - 2.days())) .get_result(&mut conn) .await; let crates_changed = crates::table - .select(crates::updated_at.ne(now - 2.days())) + .select(crates::updated_at.ne(now.into_sql::() - 2.days())) .get_result(&mut conn) .await; assert_eq!(versions_changed, Ok(false)); diff --git a/src/worker/jobs/expiry_notification.rs b/src/worker/jobs/expiry_notification.rs index a1c1945712f..eaa41c8cff4 100644 --- a/src/worker/jobs/expiry_notification.rs +++ b/src/worker/jobs/expiry_notification.rs @@ -5,6 +5,7 @@ use chrono::SecondsFormat; use crates_io_worker::BackgroundJob; use diesel::dsl::now; use diesel::prelude::*; +use diesel::sql_types::Timestamptz; use diesel_async::{AsyncPgConnection, RunQueryDsl}; use std::sync::Arc; @@ -82,7 +83,7 @@ async fn handle_expiring_token( name: &user.gh_login, token_id: token.id, token_name: &token.name, - expiry_date: token.expired_at.unwrap().and_utc(), + expiry_date: token.expired_at.unwrap(), }; emails.send(&recipient, email).await?; } else { @@ -95,7 +96,7 @@ async fn handle_expiring_token( // Update the token to prevent duplicate notifications. debug!("Marking token {} as notified…", token.id); diesel::update(token) - .set(api_tokens::expiry_notification_at.eq(now.nullable())) + .set(api_tokens::expiry_notification_at.eq(now.into_sql::().nullable())) .execute(conn) .await?; @@ -200,7 +201,8 @@ mod tests { api_tokens::user_id.eq(user.id), api_tokens::name.eq("test_token"), api_tokens::token.eq(token.hashed()), - api_tokens::expired_at.eq(now.nullable() + (EXPIRY_THRESHOLD.num_days() - 1).day()), + api_tokens::expired_at.eq(now.into_sql::().nullable() + + (EXPIRY_THRESHOLD.num_days() - 1).day()), )) .returning(ApiToken::as_returning()) .get_result(&mut conn) @@ -215,7 +217,8 @@ mod tests { api_tokens::user_id.eq(user.id), api_tokens::name.eq(format!("test_token{i}")), api_tokens::token.eq(token.hashed()), - api_tokens::expired_at.eq(now.nullable() + not_expired_offset.day()), + api_tokens::expired_at + .eq(now.into_sql::().nullable() + not_expired_offset.day()), )) .returning(ApiToken::as_returning()) .get_result(&mut conn) @@ -259,7 +262,7 @@ mod tests { api_tokens::user_id.eq(user.id), api_tokens::name.eq("expired_token"), api_tokens::token.eq(token.hashed()), - api_tokens::expired_at.eq(now.nullable() - 1.day()), + api_tokens::expired_at.eq(now.into_sql::().nullable() - 1.day()), )) .returning(ApiToken::as_returning()) .get_result(&mut conn) diff --git a/src/worker/jobs/rss/sync_crate_feed.rs b/src/worker/jobs/rss/sync_crate_feed.rs index 8fe2a4eb91a..8b62e7bf20e 100644 --- a/src/worker/jobs/rss/sync_crate_feed.rs +++ b/src/worker/jobs/rss/sync_crate_feed.rs @@ -1,7 +1,7 @@ use crate::schema::{crates, versions}; use crate::storage::FeedId; use crate::worker::Environment; -use chrono::Duration; +use chrono::{Duration, Utc}; use crates_io_worker::BackgroundJob; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; @@ -129,14 +129,14 @@ struct VersionUpdate { #[diesel(select_expression = versions::columns::num)] version: String, #[diesel(select_expression = versions::columns::created_at)] - time: chrono::NaiveDateTime, + time: chrono::DateTime, } impl VersionUpdate { fn into_rss_item(self, name: &str, domain: &str) -> rss::Item { let title = format!("New crate version published: {} v{}", name, self.version); let link = format!("https://{domain}/crates/{}/{}", name, self.version); - let pub_date = self.time.and_utc().to_rfc2822(); + let pub_date = self.time.to_rfc2822(); let guid = rss::Guid { value: link.clone(), @@ -177,7 +177,7 @@ impl VersionUpdate { #[cfg(test)] mod tests { use super::*; - use chrono::NaiveDateTime; + use chrono::DateTime; use crates_io_test_db::TestDatabase; use futures_util::future::join_all; use insta::assert_debug_snapshot; @@ -191,7 +191,7 @@ mod tests { let db = TestDatabase::new(); let mut conn = db.async_connect().await; - let now = chrono::Utc::now().naive_utc(); + let now = chrono::Utc::now(); let updates = assert_ok!(load_version_updates("foo", &mut conn).await); assert_eq!(updates.len(), 0); @@ -251,7 +251,7 @@ mod tests { conn: &mut AsyncPgConnection, crate_id: i32, version: impl Into>, - publish_time: NaiveDateTime, + publish_time: DateTime, ) -> impl Future { let version = version.into(); let future = diesel::insert_into(versions::table) diff --git a/src/worker/jobs/rss/sync_crates_feed.rs b/src/worker/jobs/rss/sync_crates_feed.rs index 133b3b692b5..c41c7bd12f3 100644 --- a/src/worker/jobs/rss/sync_crates_feed.rs +++ b/src/worker/jobs/rss/sync_crates_feed.rs @@ -1,7 +1,7 @@ use crate::schema::crates; use crate::storage::FeedId; use crate::worker::Environment; -use chrono::Duration; +use chrono::{Duration, Utc}; use crates_io_worker::BackgroundJob; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; @@ -111,14 +111,14 @@ struct NewCrate { #[diesel(select_expression = crates::columns::description)] description: Option, #[diesel(select_expression = crates::columns::created_at)] - time: chrono::NaiveDateTime, + time: chrono::DateTime, } impl NewCrate { fn into_rss_item(self, domain: &str) -> rss::Item { let title = format!("New crate created: {}", self.name); let link = format!("https://{domain}/crates/{}", self.name); - let pub_date = self.time.and_utc().to_rfc2822(); + let pub_date = self.time.to_rfc2822(); let guid = rss::Guid { value: link.clone(), @@ -151,7 +151,7 @@ impl NewCrate { #[cfg(test)] mod tests { use super::*; - use chrono::NaiveDateTime; + use chrono::DateTime; use crates_io_test_db::TestDatabase; use diesel_async::AsyncPgConnection; use futures_util::future::join_all; @@ -166,7 +166,7 @@ mod tests { let db = TestDatabase::new(); let mut conn = db.async_connect().await; - let now = chrono::Utc::now().naive_utc(); + let now = chrono::Utc::now(); let new_crates = assert_ok!(load_new_crates(&mut conn).await); assert_eq!(new_crates.len(), 0); @@ -214,7 +214,7 @@ mod tests { fn create_crate( conn: &mut AsyncPgConnection, name: impl Into>, - publish_time: NaiveDateTime, + publish_time: DateTime, ) -> impl Future { let future = diesel::insert_into(crates::table) .values(( diff --git a/src/worker/jobs/rss/sync_updates_feed.rs b/src/worker/jobs/rss/sync_updates_feed.rs index d4836f641c7..1a1c390214d 100644 --- a/src/worker/jobs/rss/sync_updates_feed.rs +++ b/src/worker/jobs/rss/sync_updates_feed.rs @@ -1,7 +1,7 @@ use crate::schema::{crates, versions}; use crate::storage::FeedId; use crate::worker::Environment; -use chrono::Duration; +use chrono::{Duration, Utc}; use crates_io_worker::BackgroundJob; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; @@ -115,7 +115,7 @@ struct VersionUpdate { #[diesel(select_expression = crates::columns::description)] description: Option, #[diesel(select_expression = versions::columns::created_at)] - time: chrono::NaiveDateTime, + time: chrono::DateTime, } impl VersionUpdate { @@ -125,7 +125,7 @@ impl VersionUpdate { self.name, self.version ); let link = format!("https://{domain}/crates/{}/{}", self.name, self.version); - let pub_date = self.time.and_utc().to_rfc2822(); + let pub_date = self.time.to_rfc2822(); let guid = rss::Guid { value: link.clone(), @@ -167,7 +167,7 @@ impl VersionUpdate { #[cfg(test)] mod tests { use super::*; - use chrono::NaiveDateTime; + use chrono::{DateTime, Utc}; use crates_io_test_db::TestDatabase; use futures_util::future::join_all; use insta::assert_debug_snapshot; @@ -181,7 +181,7 @@ mod tests { let db = TestDatabase::new(); let mut conn = db.async_connect().await; - let now = chrono::Utc::now().naive_utc(); + let now = chrono::Utc::now(); let updates = assert_ok!(load_version_updates(&mut conn).await); assert_eq!(updates.len(), 0); @@ -241,7 +241,7 @@ mod tests { conn: &mut AsyncPgConnection, crate_id: i32, version: impl Into>, - publish_time: NaiveDateTime, + publish_time: DateTime, ) -> impl Future { let version = version.into(); let future = diesel::insert_into(versions::table) diff --git a/src/worker/jobs/send_publish_notifications.rs b/src/worker/jobs/send_publish_notifications.rs index 45f5219f56a..dd64c5b9042 100644 --- a/src/worker/jobs/send_publish_notifications.rs +++ b/src/worker/jobs/send_publish_notifications.rs @@ -3,7 +3,7 @@ use crate::models::OwnerKind; use crate::schema::{crate_owners, crates, emails, users, versions}; use crate::worker::Environment; use anyhow::anyhow; -use chrono::{NaiveDateTime, SecondsFormat}; +use chrono::{DateTime, SecondsFormat, Utc}; use crates_io_worker::BackgroundJob; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; @@ -40,7 +40,6 @@ impl BackgroundJob for SendPublishNotificationsJob { let publish_time = publish_details .publish_time - .and_utc() .to_rfc3339_opts(SecondsFormat::Secs, true); // Find names and email addresses of all crate owners @@ -138,7 +137,7 @@ struct PublishDetails { #[diesel(select_expression = versions::columns::num)] version: String, #[diesel(select_expression = versions::columns::created_at)] - publish_time: NaiveDateTime, + publish_time: DateTime, #[diesel(select_expression = users::columns::gh_login.nullable())] publisher: Option, }