use vantage_types::{Entity, InvariantValue};
use crate::table::{BeforeCx, BeforeFn, Hook, Phase, Table, before_hook};
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>,
{
before_hook(move |cx: BeforeCx<'_, T>| {
let column = column.clone();
Box::pin(async move {
let absent_or_null = match cx.record.get(column.as_str()) {
None => true,
Some(v) => v.is_null(),
};
if absent_or_null {
cx.record.insert(column, T::Value::from(now_rfc3339()));
}
Ok(())
})
})
}
fn updated_hook<T: TableSource>(column: String) -> BeforeFn<T>
where
T::Value: InvariantValue + From<String>,
{
before_hook(move |cx: BeforeCx<'_, T>| {
let column = column.clone();
Box::pin(async move {
cx.record.insert(column, T::Value::from(now_rfc3339()));
Ok(())
})
})
}
fn now_rfc3339() -> String {
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
}