pylon_plugin/builtin/
soft_delete.rs1use crate::{Plugin, PluginError};
2use pylon_auth::AuthContext;
3
4pub struct SoftDeletePlugin {
8 pub field: String,
9 pub entities: Vec<String>,
11}
12
13impl SoftDeletePlugin {
14 pub fn new() -> Self {
15 Self {
16 field: "deletedAt".into(),
17 entities: vec![],
18 }
19 }
20
21 pub fn for_entities(entities: Vec<String>) -> Self {
22 Self {
23 field: "deletedAt".into(),
24 entities,
25 }
26 }
27
28 fn applies_to(&self, entity: &str) -> bool {
29 self.entities.is_empty() || self.entities.iter().any(|e| e == entity)
30 }
31}
32
33#[allow(dead_code)]
34fn now_iso() -> String {
35 use std::time::{SystemTime, UNIX_EPOCH};
36 let ts = SystemTime::now()
37 .duration_since(UNIX_EPOCH)
38 .unwrap_or_default()
39 .as_secs();
40 format!("{ts}Z")
41}
42
43impl Plugin for SoftDeletePlugin {
44 fn name(&self) -> &str {
45 "soft-delete"
46 }
47
48 fn before_delete(
49 &self,
50 entity: &str,
51 id: &str,
52 _auth: &AuthContext,
53 ) -> Result<(), PluginError> {
54 if self.applies_to(entity) {
55 Err(PluginError {
57 code: "SOFT_DELETE".into(),
58 message: format!(
59 "Entity {} uses soft delete. Set {}.{} instead of deleting row {}.",
60 entity, entity, self.field, id
61 ),
62 status: 400,
63 })
64 } else {
65 Ok(())
66 }
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn blocks_delete_for_all_entities() {
76 let plugin = SoftDeletePlugin::new();
77 let result = plugin.before_delete("Todo", "t1", &AuthContext::anonymous());
78 assert!(result.is_err());
79 assert_eq!(result.unwrap_err().code, "SOFT_DELETE");
80 }
81
82 #[test]
83 fn blocks_delete_for_specific_entities() {
84 let plugin = SoftDeletePlugin::for_entities(vec!["Todo".into()]);
85 assert!(plugin
86 .before_delete("Todo", "t1", &AuthContext::anonymous())
87 .is_err());
88 assert!(plugin
89 .before_delete("User", "u1", &AuthContext::anonymous())
90 .is_ok());
91 }
92
93 #[test]
94 fn allows_delete_for_non_matching() {
95 let plugin = SoftDeletePlugin::for_entities(vec!["Todo".into()]);
96 let result = plugin.before_delete("Comment", "c1", &AuthContext::anonymous());
97 assert!(result.is_ok());
98 }
99}