vantage_table/table/
audit.rs1use vantage_types::{Entity, InvariantValue};
14
15use crate::table::{BeforeCx, BeforeFn, Hook, Phase, Table, before_hook};
16use crate::traits::table_source::TableSource;
17
18#[derive(Clone, Debug)]
20pub struct Timestamps {
21 created_column: String,
22 updated_column: String,
23}
24
25impl Default for Timestamps {
26 fn default() -> Self {
27 Self {
28 created_column: "created_at".to_string(),
29 updated_column: "updated_at".to_string(),
30 }
31 }
32}
33
34impl Timestamps {
35 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn created(mut self, column: impl Into<String>) -> Self {
42 self.created_column = column.into();
43 self
44 }
45
46 pub fn updated(mut self, column: impl Into<String>) -> Self {
48 self.updated_column = column.into();
49 self
50 }
51}
52
53impl<T: TableSource, E: Entity<T::Value>> Table<T, E>
54where
55 T::Value: InvariantValue + From<String>,
56{
57 pub fn with_timestamps(self) -> Self {
65 self.with_audit(Timestamps::default())
66 }
67
68 pub fn with_audit(self, columns: Timestamps) -> Self {
70 self.with_hook(Hook::BeforeInsert(
71 Phase::Populate,
72 created_hook::<T>(columns.created_column),
73 ))
74 .with_hook(Hook::BeforeSave(
75 Phase::Populate,
76 updated_hook::<T>(columns.updated_column),
77 ))
78 }
79}
80
81fn created_hook<T: TableSource>(column: String) -> BeforeFn<T>
84where
85 T::Value: InvariantValue + From<String>,
86{
87 before_hook(move |cx: BeforeCx<'_, T>| {
88 let column = column.clone();
89 Box::pin(async move {
90 let absent_or_null = match cx.record.get(column.as_str()) {
91 None => true,
92 Some(v) => v.is_null(),
93 };
94 if absent_or_null {
95 cx.record.insert(column, T::Value::from(now_rfc3339()));
96 }
97 Ok(())
98 })
99 })
100}
101
102fn updated_hook<T: TableSource>(column: String) -> BeforeFn<T>
104where
105 T::Value: InvariantValue + From<String>,
106{
107 before_hook(move |cx: BeforeCx<'_, T>| {
108 let column = column.clone();
109 Box::pin(async move {
110 cx.record.insert(column, T::Value::from(now_rfc3339()));
111 Ok(())
112 })
113 })
114}
115
116fn now_rfc3339() -> String {
118 chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
119}