use std::collections::HashMap;
use std::path::Path;
use serde::{Serialize, Deserialize};
use rusqlite::{params, Connection, Result as SqlResult};
use rusqlite::OptionalExtension;
use crate::env::structs::{Endorsement, EnvType, TrustLevel, RouteInfo, EnvStatus};
use crate::middleware::env_request::EnvRequest;
use crate::middleware::env_request::EnvRequestInfo;
use crate::utils::domain_utils::{determine_parent, split_host};
use crate::utils::id_utils::generate_id_for;
use crate::env::migrate_schema::migrate_schema;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Env {
pub domain: String,
pub id: String,
pub env_type: EnvType,
pub trust: TrustLevel,
pub routes: HashMap<String, RouteInfo>,
pub parent: Option<String>,
}
impl Env {
pub fn new(domain: String, env_type: EnvType, trust: TrustLevel) -> Self {
let id = generate_id_for(&domain);
Self {
parent: determine_parent(&domain),
domain,
id,
env_type,
trust,
routes: HashMap::new(),
}
}
pub fn init_sqlite(&self, base_path: &Path) -> SqlResult<Connection> {
let db_path = base_path.join(".db");
let conn = Connection::open(db_path)?;
migrate_schema(&conn)?;
Ok(conn)
}
fn load(conn: &Connection, domain: &str) -> SqlResult<Option<Self>> {
conn.query_row(
"SELECT id, env_type, trust, parent FROM env WHERE domain = ?1",
params![domain],
|row| {
Ok(Env {
domain: domain.into(),
id: row.get(0)?,
env_type: row.get(1)?,
trust: row.get(2)?,
parent: row.get(3)?,
routes: HashMap::new(),
})
},
)
.optional()
}
pub fn from_request(conn: &Connection, req: &EnvRequest) -> SqlResult<Option<Self>> {
let host = match req {
EnvRequest::Http(r) => &r.host,
EnvRequest::Ws(r) => &r.host,
EnvRequest::Cli(_) => return Ok(None),
};
let (root, _) = split_host(host);
Self::load(conn, &root)
}
pub fn status(&self, conn: &Connection) -> SqlResult<EnvStatus> {
let endorsements = Self::get_endorsements(conn, &self.domain)?;
let dummy_req = EnvRequest::Cli(Default::default());
if endorsements.is_empty() {
return Ok(EnvStatus::PendingApproval {
env_request: EnvRequestInfo::from(&dummy_req),
reason: self.domain.clone(),
});
}
let has_approval = endorsements.iter().any(|e| e.approved);
let has_rejection = endorsements.iter().any(|e| !e.approved);
if has_approval {
Ok(EnvStatus::Approved {
env_request: EnvRequestInfo::from(&dummy_req),
})
} else if has_rejection {
Ok(EnvStatus::Blocked {
env_request: EnvRequestInfo::from(&dummy_req),
reason: self.domain.clone(),
})
} else {
Ok(EnvStatus::PendingApproval {
env_request: EnvRequestInfo::from(&dummy_req),
reason: self.domain.clone(),
})
}
}
pub fn set_metadata_sql(conn: &Connection, domain: &str, key: &str, value: &str) -> SqlResult<()> {
conn.execute(
"INSERT OR REPLACE INTO metadata (domain, key, value) VALUES (?1, ?2, ?3)",
params![domain, key, value],
)?;
Ok(())}
pub fn get_metadata_sql(conn: &Connection, domain: &str, key: &str) -> SqlResult<Option<String>> {
conn.query_row(
"SELECT value FROM metadata WHERE domain = ?1 AND key = ?2",
params![domain, key],
|row| row.get(0),
).optional()
}
pub fn add_endorsement(conn: &Connection, domain: &str, endorsement: Endorsement) -> SqlResult<()> {
conn.execute(
"INSERT OR REPLACE INTO endorsements (domain, endorser, approved, timestamp) VALUES (?1, ?2, ?3, ?4)",
params![domain, endorsement.endorser, endorsement.approved as i32, endorsement.timestamp],
)?;
Ok(())
}
pub fn get_endorsements(conn: &Connection, domain: &str) -> SqlResult<Vec<Endorsement>> {
let mut stmt = conn.prepare(
"SELECT endorser, approved, timestamp FROM endorsements WHERE domain = ?1"
)?;
let rows = stmt.query_map(params![domain], |row| {
Ok(Endorsement {
endorser: row.get(0)?,
approved: row.get::<_, i32>(1)? != 0,
timestamp: row.get(2)?,
})
})?;
let mut endorsements = Vec::new();
for endorsement in rows {
endorsements.push(endorsement?);
}
Ok(endorsements)
}
pub fn is_endorsed_by(conn: &Connection, domain: &str, endorser: &str) -> SqlResult<bool> {
conn.query_row(
"SELECT approved FROM endorsements WHERE domain = ?1 AND endorser = ?2",
params![domain, endorser],
|row| {
let approved: i32 = row.get(0)?;
Ok(approved != 0)
}
).optional().map(|opt| opt.unwrap_or(false))
}
pub fn get_parent(&self, conn: &Connection) -> SqlResult<Option<Env>> {
if let Some(ref parent_domain) = self.parent {
Env::load(conn, parent_domain)
} else {
Ok(None)
}
}
pub fn get_children(&self, conn: &Connection) -> SqlResult<Vec<Env>> {
let mut stmt = conn.prepare(
"SELECT domain, id, env_type, trust, parent FROM env WHERE parent = ?1"
)?;
let rows = stmt.query_map(params![&self.domain], |row| {
Ok(Env {
domain: row.get(0)?,
id: row.get(1)?,
env_type: row.get(2)?,
trust: row.get(3)?,
parent: row.get(4)?,
routes: HashMap::new(),
})
})?;
let mut children = Vec::new();
for env in rows {
children.push(env?);
}
Ok(children)
}
}