introspectre 1.4.0

A GraphQL offensive-security engine: introspection-driven schema analysis, active vulnerability probing, and an interactive attack-surface report.
use rusqlite::{params, Connection, Result};
use std::path::Path;

pub struct ProjectDatabase {
    conn: Connection,
}

impl ProjectDatabase {
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
        let conn = Connection::open(path)?;
        let db = Self { conn };
        db.init_tables()?;
        Ok(db)
    }

    fn init_tables(&self) -> Result<()> {
        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS projects (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL UNIQUE,
                url TEXT NOT NULL,
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP
            )",
            [],
        )?;

        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS scans (
                id INTEGER PRIMARY KEY,
                project_id INTEGER NOT NULL,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                schema_json TEXT NOT NULL,
                findings_json TEXT NOT NULL,
                stats_json TEXT NOT NULL,
                FOREIGN KEY(project_id) REFERENCES projects(id)
            )",
            [],
        )?;

        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS seeds (
                id INTEGER PRIMARY KEY,
                project_id INTEGER NOT NULL,
                field_name TEXT NOT NULL,
                type_name TEXT NOT NULL,
                value TEXT NOT NULL,
                source TEXT NOT NULL,
                UNIQUE(project_id, field_name, value),
                FOREIGN KEY(project_id) REFERENCES projects(id)
            )",
            [],
        )?;

        Ok(())
    }

    pub fn get_or_create_project(&self, name: &str, url: &str) -> Result<i64> {
        self.conn.execute(
            "INSERT OR IGNORE INTO projects (name, url) VALUES (?1, ?2)",
            params![name, url],
        )?;
        
        self.conn.query_row(
            "SELECT id FROM projects WHERE name = ?1",
            params![name],
            |row| row.get(0),
        )
    }

    pub fn save_scan(&self, project_id: i64, schema: &str, findings: &str, stats: &str) -> Result<()> {
        self.conn.execute(
            "INSERT INTO scans (project_id, schema_json, findings_json, stats_json) VALUES (?1, ?2, ?3, ?4)",
            params![project_id, schema, findings, stats],
        )?;
        Ok(())
    }

    pub fn save_seed(&self, project_id: i64, field: &str, type_name: &str, value: &str, source: &str) -> Result<()> {
        self.conn.execute(
            "INSERT OR REPLACE INTO seeds (project_id, field_name, type_name, value, source) VALUES (?1, ?2, ?3, ?4, ?5)",
            params![project_id, field, type_name, value, source],
        )?;
        Ok(())
    }

    pub fn get_seeds(&self, project_id: i64) -> Result<Vec<crate::traffic::TrafficSeed>> {
        let mut stmt = self.conn.prepare(
            "SELECT field_name, type_name, value, source FROM seeds WHERE project_id = ?1"
        )?;
        let seed_iter = stmt.query_map(params![project_id], |row| {
            Ok(crate::traffic::TrafficSeed {
                field_name: row.get(0)?,
                type_name: row.get(1)?,
                value: row.get(2)?,
                source: row.get(3)?,
            })
        })?;

        let mut seeds = Vec::new();
        for seed in seed_iter {
            seeds.push(seed?);
        }
        Ok(seeds)
    }
}