241 lines
6.3 KiB
Rust
241 lines
6.3 KiB
Rust
|
use crate::store::StoreError;
|
||
|
use chrono::{DateTime, Utc};
|
||
|
use sled::{Db, Transactional, Tree};
|
||
|
use uuid::Uuid;
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
pub struct Server {
|
||
|
id: Uuid,
|
||
|
domain: String,
|
||
|
created: DateTime<Utc>,
|
||
|
title: Option<String>,
|
||
|
description: Option<String>,
|
||
|
}
|
||
|
|
||
|
impl Server {
|
||
|
pub fn id(&self) -> Uuid {
|
||
|
self.id
|
||
|
}
|
||
|
|
||
|
pub fn domain(&self) -> &str {
|
||
|
&self.domain
|
||
|
}
|
||
|
|
||
|
pub fn title(&self) -> Option<&str> {
|
||
|
self.title.as_deref()
|
||
|
}
|
||
|
|
||
|
pub fn description(&self) -> Option<&str> {
|
||
|
self.description.as_deref()
|
||
|
}
|
||
|
|
||
|
pub fn created(&self) -> DateTime<Utc> {
|
||
|
self.created
|
||
|
}
|
||
|
|
||
|
pub(crate) fn changes(&self) -> ServerChanges {
|
||
|
ServerChanges {
|
||
|
id: self.id,
|
||
|
title: None,
|
||
|
description: None,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub struct ServerChanges {
|
||
|
id: Uuid,
|
||
|
title: Option<String>,
|
||
|
description: Option<String>,
|
||
|
}
|
||
|
|
||
|
impl ServerChanges {
|
||
|
pub fn title(&mut self, title: &str) -> &mut Self {
|
||
|
self.title = Some(title.to_owned());
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn description(&mut self, description: &str) -> &mut Self {
|
||
|
self.description = Some(description.to_owned());
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub(crate) fn any_changes(&self) -> bool {
|
||
|
self.title.is_some() || self.description.is_some()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Clone)]
|
||
|
pub struct Store {
|
||
|
created_id: Tree,
|
||
|
domain_id: Tree,
|
||
|
|
||
|
id_domain: Tree,
|
||
|
id_created: Tree,
|
||
|
id_title: Tree,
|
||
|
id_description: Tree,
|
||
|
|
||
|
self_server: Tree,
|
||
|
}
|
||
|
|
||
|
impl Store {
|
||
|
pub(super) fn build(db: &Db) -> Result<Self, sled::Error> {
|
||
|
let created_id = db.open_tree("/profiles/servers/created_id")?;
|
||
|
let domain_id = db.open_tree("/profiles/servers/domain_id")?;
|
||
|
let id_domain = db.open_tree("/profiles/servers/id_domain")?;
|
||
|
let id_created = db.open_tree("/profiles/servers/id_created")?;
|
||
|
let id_title = db.open_tree("/profiles/servers/id_title")?;
|
||
|
let id_description = db.open_tree("/profiles/servers/id_description")?;
|
||
|
let self_server = db.open_tree("/profiles/servers/self_server")?;
|
||
|
|
||
|
Ok(Store {
|
||
|
created_id,
|
||
|
domain_id,
|
||
|
id_domain,
|
||
|
id_created,
|
||
|
id_title,
|
||
|
id_description,
|
||
|
self_server,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
pub(crate) fn self_exists(&self) -> bool {
|
||
|
self.self_server.iter().next().is_some()
|
||
|
}
|
||
|
|
||
|
pub(crate) fn set_self(&self, id: Uuid) -> Result<(), StoreError> {
|
||
|
self.self_server.insert("self", id.as_bytes())?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
pub(crate) fn is_self(&self, id: Uuid) -> Result<bool, StoreError> {
|
||
|
Ok(self
|
||
|
.self_server
|
||
|
.get("self")?
|
||
|
.and_then(uuid_from_ivec)
|
||
|
.map(|self_id| self_id == id)
|
||
|
.unwrap_or(false))
|
||
|
}
|
||
|
|
||
|
pub(crate) fn create(
|
||
|
&self,
|
||
|
domain: &str,
|
||
|
created: DateTime<Utc>,
|
||
|
) -> Result<Server, StoreError> {
|
||
|
let mut id;
|
||
|
|
||
|
while {
|
||
|
id = Uuid::new_v4();
|
||
|
|
||
|
self.id_domain
|
||
|
.compare_and_swap(
|
||
|
id.as_bytes(),
|
||
|
None as Option<&[u8]>,
|
||
|
Some(domain.as_bytes()),
|
||
|
)?
|
||
|
.is_err()
|
||
|
} {}
|
||
|
|
||
|
let domain_clone = domain.to_owned();
|
||
|
let res = [&self.created_id, &self.domain_id, &self.id_created].transaction(move |trees| {
|
||
|
let created_id = &trees[0];
|
||
|
let domain_id = &trees[1];
|
||
|
let id_created = &trees[2];
|
||
|
|
||
|
created_id.insert(created.to_rfc3339().as_bytes(), id.as_bytes())?;
|
||
|
domain_id.insert(domain_clone.as_bytes(), id.as_bytes())?;
|
||
|
id_created.insert(id.as_bytes(), created.to_rfc3339().as_bytes())?;
|
||
|
|
||
|
Ok(())
|
||
|
});
|
||
|
|
||
|
if let Err(e) = res {
|
||
|
self.id_domain.remove(id.as_bytes())?;
|
||
|
return Err(e.into());
|
||
|
}
|
||
|
|
||
|
Ok(Server {
|
||
|
id,
|
||
|
domain: domain.to_owned(),
|
||
|
title: None,
|
||
|
description: None,
|
||
|
created,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
pub(crate) fn update(&self, changes: &ServerChanges) -> Result<Server, StoreError> {
|
||
|
let domain = match self
|
||
|
.id_domain
|
||
|
.get(changes.id.as_bytes())?
|
||
|
.map(string_from_ivec)
|
||
|
{
|
||
|
Some(domain) => domain,
|
||
|
None => return Err(StoreError::Missing),
|
||
|
};
|
||
|
let created = match self
|
||
|
.id_created
|
||
|
.get(changes.id.as_bytes())?
|
||
|
.and_then(date_from_ivec)
|
||
|
{
|
||
|
Some(date) => date,
|
||
|
None => return Err(StoreError::Missing),
|
||
|
};
|
||
|
|
||
|
if let Some(title) = &changes.title {
|
||
|
self.id_title
|
||
|
.insert(changes.id.as_bytes(), title.as_bytes())?;
|
||
|
}
|
||
|
if let Some(description) = &changes.description {
|
||
|
self.id_description
|
||
|
.insert(changes.id.as_bytes(), description.as_bytes())?;
|
||
|
}
|
||
|
|
||
|
Ok(Server {
|
||
|
id: changes.id,
|
||
|
domain,
|
||
|
created,
|
||
|
title: changes.title.clone(),
|
||
|
description: changes.description.clone(),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
pub fn get_self(&self) -> Result<Option<Uuid>, StoreError> {
|
||
|
Ok(self.self_server.get("self")?.and_then(uuid_from_ivec))
|
||
|
}
|
||
|
|
||
|
pub fn by_id(&self, id: Uuid) -> Result<Option<Server>, StoreError> {
|
||
|
let domain = self.id_domain.get(id.as_bytes())?.map(string_from_ivec);
|
||
|
let created = self.id_created.get(id.as_bytes())?.and_then(date_from_ivec);
|
||
|
let title = self.id_title.get(id.as_bytes())?.map(string_from_ivec);
|
||
|
let description = self
|
||
|
.id_description
|
||
|
.get(id.as_bytes())?
|
||
|
.map(string_from_ivec);
|
||
|
|
||
|
Ok(domain.and_then(|domain| {
|
||
|
created.map(|created| Server {
|
||
|
id,
|
||
|
domain,
|
||
|
title,
|
||
|
description,
|
||
|
created,
|
||
|
})
|
||
|
}))
|
||
|
}
|
||
|
|
||
|
pub(crate) fn by_domain(&self, domain: &str) -> Result<Option<Uuid>, StoreError> {
|
||
|
Ok(self.domain_id.get(domain)?.and_then(uuid_from_ivec))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn string_from_ivec(ivec: sled::IVec) -> String {
|
||
|
String::from_utf8_lossy(&ivec).to_string()
|
||
|
}
|
||
|
|
||
|
fn uuid_from_ivec(ivec: sled::IVec) -> Option<Uuid> {
|
||
|
Uuid::from_slice(&ivec).ok()
|
||
|
}
|
||
|
|
||
|
fn date_from_ivec(ivec: sled::IVec) -> Option<DateTime<Utc>> {
|
||
|
String::from_utf8_lossy(&ivec).parse().ok()
|
||
|
}
|