vantage_table/table/
audit.rs1use std::future::Future;
14use std::pin::Pin;
15use std::sync::Arc;
16
17use vantage_core::Result;
18use vantage_types::{EmptyEntity, Entity, InvariantValue, Record};
19
20use crate::table::{BeforeFn, Hook, Phase, Table};
21use crate::traits::table_source::TableSource;
22
23#[derive(Clone, Debug)]
25pub struct Timestamps {
26 created_column: String,
27 updated_column: String,
28}
29
30impl Default for Timestamps {
31 fn default() -> Self {
32 Self {
33 created_column: "created_at".to_string(),
34 updated_column: "updated_at".to_string(),
35 }
36 }
37}
38
39impl Timestamps {
40 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn created(mut self, column: impl Into<String>) -> Self {
47 self.created_column = column.into();
48 self
49 }
50
51 pub fn updated(mut self, column: impl Into<String>) -> Self {
53 self.updated_column = column.into();
54 self
55 }
56}
57
58impl<T: TableSource, E: Entity<T::Value>> Table<T, E>
59where
60 T::Value: InvariantValue + From<String>,
61{
62 pub fn with_timestamps(self) -> Self {
70 self.with_audit(Timestamps::default())
71 }
72
73 pub fn with_audit(self, columns: Timestamps) -> Self {
75 self.with_hook(Hook::BeforeInsert(
76 Phase::Populate,
77 created_hook::<T>(columns.created_column),
78 ))
79 .with_hook(Hook::BeforeSave(
80 Phase::Populate,
81 updated_hook::<T>(columns.updated_column),
82 ))
83 }
84}
85
86fn created_hook<T: TableSource>(column: String) -> BeforeFn<T>
89where
90 T::Value: InvariantValue + From<String>,
91{
92 Arc::new(
93 move |rec: &mut Record<T::Value>,
94 _table: &Table<T, EmptyEntity>|
95 -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
96 let column = column.clone();
97 Box::pin(async move {
98 let absent_or_null = match rec.get(column.as_str()) {
99 None => true,
100 Some(v) => v.is_null(),
101 };
102 if absent_or_null {
103 rec.insert(column, T::Value::from(now_rfc3339()));
104 }
105 Ok(())
106 })
107 },
108 )
109}
110
111fn updated_hook<T: TableSource>(column: String) -> BeforeFn<T>
113where
114 T::Value: InvariantValue + From<String>,
115{
116 Arc::new(
117 move |rec: &mut Record<T::Value>,
118 _table: &Table<T, EmptyEntity>|
119 -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
120 let column = column.clone();
121 Box::pin(async move {
122 rec.insert(column, T::Value::from(now_rfc3339()));
123 Ok(())
124 })
125 },
126 )
127}
128
129fn now_rfc3339() -> String {
131 chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
132}