use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use vantage_core::Result;
use vantage_types::{EmptyEntity, Entity, InvariantValue, Record};
use crate::table::{BeforeFn, Hook, Phase, Table};
use crate::traits::table_source::TableSource;
#[derive(Clone, Debug)]
pub struct Timestamps {
created_column: String,
updated_column: String,
}
impl Default for Timestamps {
fn default() -> Self {
Self {
created_column: "created_at".to_string(),
updated_column: "updated_at".to_string(),
}
}
}
impl Timestamps {
pub fn new() -> Self {
Self::default()
}
pub fn created(mut self, column: impl Into<String>) -> Self {
self.created_column = column.into();
self
}
pub fn updated(mut self, column: impl Into<String>) -> Self {
self.updated_column = column.into();
self
}
}
impl<T: TableSource, E: Entity<T::Value>> Table<T, E>
where
T::Value: InvariantValue + From<String>,
{
pub fn with_timestamps(self) -> Self {
self.with_audit(Timestamps::default())
}
pub fn with_audit(self, columns: Timestamps) -> Self {
self.with_hook(Hook::BeforeInsert(
Phase::Populate,
created_hook::<T>(columns.created_column),
))
.with_hook(Hook::BeforeSave(
Phase::Populate,
updated_hook::<T>(columns.updated_column),
))
}
}
fn created_hook<T: TableSource>(column: String) -> BeforeFn<T>
where
T::Value: InvariantValue + From<String>,
{
Arc::new(
move |rec: &mut Record<T::Value>,
_table: &Table<T, EmptyEntity>|
-> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
let column = column.clone();
Box::pin(async move {
let absent_or_null = match rec.get(column.as_str()) {
None => true,
Some(v) => v.is_null(),
};
if absent_or_null {
rec.insert(column, T::Value::from(now_rfc3339()));
}
Ok(())
})
},
)
}
fn updated_hook<T: TableSource>(column: String) -> BeforeFn<T>
where
T::Value: InvariantValue + From<String>,
{
Arc::new(
move |rec: &mut Record<T::Value>,
_table: &Table<T, EmptyEntity>|
-> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
let column = column.clone();
Box::pin(async move {
rec.insert(column, T::Value::from(now_rfc3339()));
Ok(())
})
},
)
}
fn now_rfc3339() -> String {
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
}