Skip to main content

ploit_malper/db/
mod.rs

1pub mod content;
2pub mod models;
3pub mod pocketbase;
4pub mod schema;
5pub mod sqlite;
6
7use crate::config::DatabaseConfig;
8use crate::error::Result;
9use models::{Asset, Finding, Observation, ScanRun, Service};
10
11/// Collection names used by every backend. Kept intentionally small: bulk
12/// content (full reports, long finding evidence) is stored as files in the
13/// content store, so the database only holds metadata and short fields.
14pub mod collections {
15    pub const ASSETS: &str = "Assets";
16    pub const SERVICES: &str = "Services";
17    pub const FINDINGS: &str = "Findings";
18    pub const OBSERVATIONS: &str = "Observations";
19    pub const SCAN_RUNS: &str = "ScanRuns";
20
21    pub const ALL: [&str; 5] = [ASSETS, SERVICES, FINDINGS, OBSERVATIONS, SCAN_RUNS];
22}
23
24/// Backend-agnostic persistence interface used by the importer.
25///
26/// All write operations are upserts keyed by a stable, content-derived id so
27/// that re-running an import over the same scan folder is idempotent. History
28/// is preserved through the `Observations` collection rather than by
29/// overwriting prior rows.
30pub trait Storage: Send {
31    fn kind(&self) -> &str;
32
33    /// Verify connectivity (PocketBase health / SQLite open + query).
34    fn ping(&mut self) -> Result<bool>;
35
36    /// Create all required collections/tables if they do not exist.
37    fn ensure_schema(&mut self) -> Result<()>;
38
39    // --- Scan runs ------------------------------------------------------
40
41    fn upsert_scan_run(&mut self, run: &ScanRun) -> Result<()>;
42    fn get_scan_run(&mut self, run_id: &str) -> Result<Option<ScanRun>>;
43    fn list_scan_runs(&mut self) -> Result<Vec<ScanRun>>;
44
45    // --- Assets ---------------------------------------------------------
46
47    fn upsert_asset(&mut self, asset: &Asset) -> Result<()>;
48    fn get_asset(&mut self, asset_id: &str) -> Result<Option<Asset>>;
49    fn list_assets(&mut self) -> Result<Vec<Asset>>;
50
51    // --- Services -------------------------------------------------------
52
53    fn upsert_service(&mut self, service: &Service) -> Result<()>;
54    fn get_service(&mut self, service_id: &str) -> Result<Option<Service>>;
55    fn list_services_for_asset(&mut self, asset_id: &str) -> Result<Vec<Service>>;
56    fn list_all_services(&mut self) -> Result<Vec<Service>>;
57
58    // --- Findings -------------------------------------------------------
59
60    fn upsert_finding(&mut self, finding: &Finding) -> Result<()>;
61    fn get_finding(&mut self, finding_id: &str) -> Result<Option<Finding>>;
62    fn list_findings_for_asset(&mut self, asset_id: &str) -> Result<Vec<Finding>>;
63    fn list_all_findings(&mut self) -> Result<Vec<Finding>>;
64
65    // --- Observations ---------------------------------------------------
66
67    fn add_observation(&mut self, observation: &Observation) -> Result<()>;
68    fn get_observation(&mut self, obs_id: &str) -> Result<Option<Observation>>;
69    fn list_observations_for(
70        &mut self,
71        subject_type: &str,
72        subject_id: &str,
73    ) -> Result<Vec<Observation>>;
74    fn list_all_observations(&mut self) -> Result<Vec<Observation>>;
75}
76
77/// Open a storage backend based on the persisted configuration.
78pub fn open_storage(config: &DatabaseConfig) -> Result<Box<dyn Storage>> {
79    if config.backend == "sqlite" {
80        Ok(Box::new(sqlite::SqliteStorage::open(&config.sqlite_path)?))
81    } else {
82        Ok(Box::new(pocketbase::PocketBaseStorage::new(config)?))
83    }
84}
85
86/// Open a storage backend, overriding the configured backend.
87pub fn open_storage_with(
88    config: &DatabaseConfig,
89    backend: &str,
90    pocketbase_url: Option<&str>,
91    sqlite_path: Option<&str>,
92) -> Result<Box<dyn Storage>> {
93    if backend == "sqlite" {
94        let path = sqlite_path
95            .map(str::to_string)
96            .unwrap_or_else(|| config.sqlite_path.clone());
97        Ok(Box::new(sqlite::SqliteStorage::open(&path)?))
98    } else {
99        let mut override_config = config.clone();
100        if let Some(url) = pocketbase_url {
101            override_config.pocketbase_url = url.to_string();
102        }
103        Ok(Box::new(pocketbase::PocketBaseStorage::new(
104            &override_config,
105        )?))
106    }
107}