2021-02-08 23:55:45 +00:00
|
|
|
use super::{StoreError, TermSearch, Undo};
|
2021-01-04 17:34:31 +00:00
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
use sled::{Db, Transactional, Tree};
|
|
|
|
use std::io::Cursor;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
2021-02-08 23:55:45 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ProfileChanges {
|
|
|
|
id: Uuid,
|
|
|
|
display_name: Option<String>,
|
|
|
|
display_name_source: Option<String>,
|
|
|
|
description: Option<String>,
|
|
|
|
description_source: Option<String>,
|
|
|
|
login_required: Option<bool>,
|
|
|
|
icon: Option<Uuid>,
|
|
|
|
banner: Option<Uuid>,
|
2021-02-09 00:23:09 +00:00
|
|
|
updated: Option<DateTime<Utc>>,
|
2021-02-08 23:55:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum OwnerSource {
|
|
|
|
Local(Uuid),
|
|
|
|
Remote(Uuid),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Profile {
|
|
|
|
id: Uuid,
|
|
|
|
owner_source: OwnerSource,
|
|
|
|
handle: String,
|
|
|
|
domain: String,
|
|
|
|
display_name: Option<String>,
|
|
|
|
display_name_source: Option<String>,
|
|
|
|
description: Option<String>,
|
|
|
|
description_source: Option<String>,
|
|
|
|
icon: Option<Uuid>,
|
|
|
|
banner: Option<Uuid>,
|
|
|
|
published: DateTime<Utc>,
|
2021-02-09 00:23:09 +00:00
|
|
|
updated: Option<DateTime<Utc>>,
|
2021-02-08 23:55:45 +00:00
|
|
|
login_required: bool,
|
|
|
|
suspended: bool,
|
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Store {
|
|
|
|
profile_tree: Tree,
|
|
|
|
handle_tree: Tree,
|
|
|
|
owner_created_tree: Tree,
|
|
|
|
created_tree: Tree,
|
|
|
|
count_tree: Tree,
|
2021-01-26 02:37:36 +00:00
|
|
|
handle_index: TermSearch,
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2021-01-12 03:58:03 +00:00
|
|
|
enum StoredOwnerSource {
|
2021-01-04 17:34:31 +00:00
|
|
|
Local(Uuid),
|
2021-01-16 04:25:06 +00:00
|
|
|
Remote(Uuid),
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
2021-01-28 02:58:38 +00:00
|
|
|
struct StoredProfile {
|
2021-01-04 17:34:31 +00:00
|
|
|
id: Uuid,
|
2021-01-12 03:58:03 +00:00
|
|
|
owner_source: StoredOwnerSource,
|
2021-01-28 02:58:38 +00:00
|
|
|
handle: String,
|
|
|
|
domain: String,
|
2021-01-14 04:43:43 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-12 03:58:03 +00:00
|
|
|
display_name: Option<String>,
|
2021-01-14 04:43:43 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-28 02:58:38 +00:00
|
|
|
display_name_source: Option<String>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-12 03:58:03 +00:00
|
|
|
description: Option<String>,
|
2021-01-14 04:43:43 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-28 02:58:38 +00:00
|
|
|
description_source: Option<String>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-04 17:34:31 +00:00
|
|
|
icon: Option<Uuid>,
|
2021-01-14 04:43:43 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2021-01-04 17:34:31 +00:00
|
|
|
banner: Option<Uuid>,
|
|
|
|
published: DateTime<Utc>,
|
2021-02-09 00:23:09 +00:00
|
|
|
updated: Option<DateTime<Utc>>,
|
2021-01-04 17:34:31 +00:00
|
|
|
login_required: bool,
|
|
|
|
created_at: DateTime<Utc>,
|
2021-01-14 04:43:43 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
suspended_at: Option<DateTime<Utc>>,
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-02-08 23:55:45 +00:00
|
|
|
impl ProfileChanges {
|
|
|
|
pub(crate) fn display_name(&mut self, display_name: &str) -> &mut Self {
|
|
|
|
self.display_name = Some(display_name.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn display_name_source(&mut self, display_name_source: &str) -> &mut Self {
|
|
|
|
self.display_name_source = Some(display_name_source.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn description(&mut self, description: &str) -> &mut Self {
|
|
|
|
self.description = Some(description.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn description_source(&mut self, description_source: &str) -> &mut Self {
|
|
|
|
self.description_source = Some(description_source.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn login_required(&mut self, required: bool) -> &mut Self {
|
|
|
|
self.login_required = Some(required);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn icon(&mut self, file_id: Uuid) -> &mut Self {
|
|
|
|
self.icon = Some(file_id);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn banner(&mut self, file_id: Uuid) -> &mut Self {
|
|
|
|
self.banner = Some(file_id);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-02-09 00:23:09 +00:00
|
|
|
pub(crate) fn updated(&mut self, updated: DateTime<Utc>) -> &mut Self {
|
|
|
|
self.updated = Some(updated);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-02-08 23:55:45 +00:00
|
|
|
pub(crate) fn any_changes(&self) -> bool {
|
|
|
|
self.display_name.is_some()
|
|
|
|
|| self.description.is_some()
|
|
|
|
|| self.login_required.is_some()
|
|
|
|
|| self.icon.is_some()
|
|
|
|
|| self.banner.is_some()
|
2021-02-09 00:23:09 +00:00
|
|
|
|| self.updated.is_some()
|
2021-02-08 23:55:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl OwnerSource {
|
|
|
|
pub fn is_local(&self) -> bool {
|
|
|
|
matches!(self, OwnerSource::Local(_))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Profile {
|
|
|
|
pub fn update(&self) -> ProfileChanges {
|
|
|
|
ProfileChanges {
|
|
|
|
id: self.id,
|
|
|
|
display_name: None,
|
|
|
|
display_name_source: None,
|
|
|
|
description: None,
|
|
|
|
description_source: None,
|
|
|
|
login_required: None,
|
|
|
|
icon: None,
|
|
|
|
banner: None,
|
2021-02-09 00:23:09 +00:00
|
|
|
updated: None,
|
2021-02-08 23:55:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn id(&self) -> Uuid {
|
|
|
|
self.id
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn owner_source(&self) -> &OwnerSource {
|
|
|
|
&self.owner_source
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn local_owner(&self) -> Option<Uuid> {
|
|
|
|
match self.owner_source {
|
|
|
|
OwnerSource::Local(id) => Some(id),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle(&self) -> &str {
|
|
|
|
&self.handle
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn domain(&self) -> &str {
|
|
|
|
&self.domain
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn display_name(&self) -> Option<&str> {
|
|
|
|
self.display_name.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn display_name_source(&self) -> Option<&str> {
|
|
|
|
self.display_name_source.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn description(&self) -> Option<&str> {
|
|
|
|
self.description.as_ref().map(|d| d.as_str())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn description_source(&self) -> Option<&str> {
|
|
|
|
self.description_source.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn icon(&self) -> Option<Uuid> {
|
|
|
|
self.icon
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn banner(&self) -> Option<Uuid> {
|
|
|
|
self.banner
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn published(&self) -> DateTime<Utc> {
|
|
|
|
self.published
|
|
|
|
}
|
|
|
|
|
2021-02-09 00:23:09 +00:00
|
|
|
pub fn updated(&self) -> Option<DateTime<Utc>> {
|
|
|
|
self.updated
|
|
|
|
}
|
|
|
|
|
2021-02-08 23:55:45 +00:00
|
|
|
pub fn login_required(&self) -> bool {
|
|
|
|
self.login_required
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_suspended(&self) -> bool {
|
|
|
|
self.suspended
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
impl Store {
|
2021-01-14 04:43:43 +00:00
|
|
|
pub(super) fn build(db: &Db) -> Result<Self, sled::Error> {
|
2021-01-04 17:34:31 +00:00
|
|
|
Ok(Store {
|
|
|
|
profile_tree: db.open_tree("profiles/profiles")?,
|
|
|
|
handle_tree: db.open_tree("profiles/profiles/handles")?,
|
|
|
|
owner_created_tree: db.open_tree("/profiles/profiles/owner/created")?,
|
|
|
|
created_tree: db.open_tree("/profiles/profiles/created")?,
|
|
|
|
count_tree: db.open_tree("/profiles/profiles/count")?,
|
2021-01-26 02:37:36 +00:00
|
|
|
handle_index: TermSearch::build("handle", 20, db)?,
|
2021-01-04 17:34:31 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-01-14 04:43:43 +00:00
|
|
|
pub(crate) fn create(
|
2021-01-04 17:34:31 +00:00
|
|
|
&self,
|
|
|
|
source: OwnerSource,
|
|
|
|
handle: &str,
|
|
|
|
domain: &str,
|
|
|
|
published: DateTime<Utc>,
|
|
|
|
) -> Result<Profile, StoreError> {
|
2021-02-09 00:49:14 +00:00
|
|
|
let handle_lower = handle.to_lowercase();
|
2021-01-26 02:37:36 +00:00
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
let mut id;
|
|
|
|
let mut stored_profile;
|
|
|
|
|
|
|
|
let now = Utc::now().into();
|
|
|
|
let mut stored_profile_vec = vec![];
|
|
|
|
|
|
|
|
let stored_source = match &source {
|
|
|
|
OwnerSource::Local(id) => StoredOwnerSource::Local(*id),
|
2021-01-16 04:25:06 +00:00
|
|
|
OwnerSource::Remote(id) => StoredOwnerSource::Remote(*id),
|
2021-01-04 17:34:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
while {
|
|
|
|
stored_profile_vec.clear();
|
|
|
|
let writer = Cursor::new(&mut stored_profile_vec);
|
|
|
|
id = Uuid::new_v4();
|
|
|
|
stored_profile = StoredProfile {
|
|
|
|
id,
|
|
|
|
owner_source: stored_source.clone(),
|
2021-01-28 02:58:38 +00:00
|
|
|
handle: handle.to_owned(),
|
|
|
|
domain: domain.to_owned(),
|
2021-01-04 17:34:31 +00:00
|
|
|
display_name: None,
|
2021-01-28 02:58:38 +00:00
|
|
|
display_name_source: None,
|
2021-01-04 17:34:31 +00:00
|
|
|
description: None,
|
2021-01-28 02:58:38 +00:00
|
|
|
description_source: None,
|
2021-01-04 17:34:31 +00:00
|
|
|
icon: None,
|
|
|
|
banner: None,
|
|
|
|
published,
|
2021-02-09 00:23:09 +00:00
|
|
|
updated: None,
|
2021-01-04 17:34:31 +00:00
|
|
|
login_required: true,
|
|
|
|
created_at: now,
|
2021-01-14 04:43:43 +00:00
|
|
|
suspended_at: None,
|
2021-01-04 17:34:31 +00:00
|
|
|
};
|
|
|
|
serde_json::to_writer(writer, &stored_profile)?;
|
|
|
|
self.profile_tree
|
|
|
|
.compare_and_swap(
|
|
|
|
id_profile_key(id),
|
|
|
|
None as Option<&[u8]>,
|
|
|
|
Some(stored_profile_vec.as_slice()),
|
|
|
|
)?
|
|
|
|
.is_err()
|
|
|
|
} {}
|
|
|
|
|
|
|
|
let source = source.clone();
|
|
|
|
|
|
|
|
// ensure handle uniqueness
|
|
|
|
match self.handle_tree.compare_and_swap(
|
2021-02-09 00:49:14 +00:00
|
|
|
handle_id_key(&handle_lower, domain),
|
2021-01-04 17:34:31 +00:00
|
|
|
None as Option<&[u8]>,
|
2021-01-26 02:37:36 +00:00
|
|
|
Some(id.as_bytes()),
|
2021-01-04 17:34:31 +00:00
|
|
|
) {
|
|
|
|
Ok(Ok(_)) => (),
|
|
|
|
Ok(Err(_)) => {
|
|
|
|
self.profile_tree.remove(id_profile_key(id))?;
|
|
|
|
return Err(StoreError::DoubleStore);
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
self.profile_tree.remove(id_profile_key(id))?;
|
|
|
|
return Err(e.into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let res = [
|
|
|
|
&self.created_tree,
|
|
|
|
&self.owner_created_tree,
|
|
|
|
&self.count_tree,
|
|
|
|
]
|
|
|
|
.transaction(move |trees| {
|
2021-01-27 02:29:18 +00:00
|
|
|
let created_tree = &trees[0];
|
|
|
|
let owner_created_tree = &trees[1];
|
|
|
|
let count_tree = &trees[2];
|
2021-01-04 17:34:31 +00:00
|
|
|
|
2021-01-26 02:37:36 +00:00
|
|
|
created_tree.insert(created_profile_key(now, id).as_bytes(), id.as_bytes())?;
|
2021-01-04 17:34:31 +00:00
|
|
|
owner_created_tree.insert(
|
|
|
|
owner_created_profile_key(&source, now, id).as_bytes(),
|
2021-01-26 02:37:36 +00:00
|
|
|
id.as_bytes(),
|
2021-01-04 17:34:31 +00:00
|
|
|
)?;
|
|
|
|
|
|
|
|
super::count(count_tree, &owner_profile_count_key(&source), |c| {
|
|
|
|
c.saturating_add(1)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
});
|
|
|
|
|
2021-02-09 00:49:14 +00:00
|
|
|
if let Err(e) = self.handle_index.insert(&handle_lower) {
|
|
|
|
log::error!("Failed to add {} to search index: {}", handle_lower, e);
|
2021-01-26 02:37:36 +00:00
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
if let Err(e) = res {
|
|
|
|
[&self.profile_tree, &self.handle_tree].transaction(move |trees| {
|
|
|
|
let profile_tree = &trees[0];
|
|
|
|
let handle_tree = &trees[1];
|
|
|
|
|
|
|
|
profile_tree.remove(id_profile_key(id).as_bytes())?;
|
2021-02-09 00:49:14 +00:00
|
|
|
handle_tree.remove(handle_id_key(&handle_lower, domain).as_bytes())?;
|
2021-01-04 17:34:31 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
return Err(e.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(stored_profile.into())
|
|
|
|
}
|
|
|
|
|
2021-01-26 02:37:36 +00:00
|
|
|
pub fn search<'a>(
|
|
|
|
&'a self,
|
|
|
|
handle_term: &'a str,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
|
|
|
|
self.handle_index
|
|
|
|
.search(handle_term)
|
|
|
|
.flat_map(move |handle| self.handle_iter(handle))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_iter<'a>(&'a self, handle: String) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
|
|
|
|
self.handle_tree
|
|
|
|
.scan_prefix(handle_id_prefix(&handle))
|
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
|
|
|
.filter_map(|ivec| Uuid::from_slice(&ivec).ok())
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
pub fn count(&self, source: OwnerSource) -> Result<u64, StoreError> {
|
|
|
|
match self.count_tree.get(owner_profile_count_key(&source))? {
|
2021-01-26 02:37:36 +00:00
|
|
|
Some(ivec) => Ok(String::from_utf8_lossy(&ivec).parse::<u64>().unwrap_or(0)),
|
2021-01-04 17:34:31 +00:00
|
|
|
None => Ok(0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-14 04:43:43 +00:00
|
|
|
pub(crate) fn update(&self, profile_changes: &ProfileChanges) -> Result<Profile, StoreError> {
|
2021-01-04 17:34:31 +00:00
|
|
|
let stored_profile_ivec = match self
|
|
|
|
.profile_tree
|
|
|
|
.get(id_profile_key(profile_changes.id).as_bytes())?
|
|
|
|
{
|
|
|
|
Some(ivec) => ivec,
|
|
|
|
None => return Err(StoreError::Missing),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut stored_profile: StoredProfile = serde_json::from_slice(&stored_profile_ivec)?;
|
|
|
|
|
2021-02-09 00:23:09 +00:00
|
|
|
if let Some(updated) = profile_changes.updated {
|
|
|
|
let previously_updated = stored_profile.updated.unwrap_or(stored_profile.published);
|
|
|
|
if updated < previously_updated {
|
|
|
|
return Err(StoreError::Outdated);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-09 04:34:13 +00:00
|
|
|
if let Some(login_required) = profile_changes.login_required {
|
|
|
|
stored_profile.login_required = login_required;
|
|
|
|
}
|
2021-01-28 02:58:38 +00:00
|
|
|
if let Some(display_name) = &profile_changes.display_name {
|
2021-01-12 03:58:03 +00:00
|
|
|
stored_profile.display_name = Some(display_name.clone());
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
2021-01-28 02:58:38 +00:00
|
|
|
if let Some(display_name_source) = &profile_changes.display_name_source {
|
|
|
|
stored_profile.display_name_source = Some(display_name_source.clone());
|
|
|
|
}
|
|
|
|
if let Some(description) = &profile_changes.description {
|
2021-01-12 03:58:03 +00:00
|
|
|
stored_profile.description = Some(description.clone());
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
2021-01-28 02:58:38 +00:00
|
|
|
if let Some(description_source) = &profile_changes.description_source {
|
|
|
|
stored_profile.description_source = Some(description_source.clone());
|
|
|
|
}
|
2021-02-08 23:55:45 +00:00
|
|
|
if let Some(icon) = profile_changes.icon {
|
2021-01-04 17:34:31 +00:00
|
|
|
stored_profile.icon = Some(icon);
|
|
|
|
}
|
2021-02-08 23:55:45 +00:00
|
|
|
if let Some(banner) = profile_changes.banner {
|
2021-01-04 17:34:31 +00:00
|
|
|
stored_profile.banner = Some(banner);
|
|
|
|
}
|
2021-02-09 00:23:09 +00:00
|
|
|
if let Some(updated) = profile_changes.updated {
|
|
|
|
stored_profile.updated = Some(updated);
|
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
|
|
|
|
let stored_profile_vec = serde_json::to_vec(&stored_profile)?;
|
|
|
|
|
|
|
|
if self
|
|
|
|
.profile_tree
|
|
|
|
.compare_and_swap(
|
2021-02-08 23:55:45 +00:00
|
|
|
id_profile_key(profile_changes.id).as_bytes(),
|
2021-01-04 17:34:31 +00:00
|
|
|
Some(&stored_profile_ivec),
|
|
|
|
Some(stored_profile_vec),
|
|
|
|
)?
|
|
|
|
.is_err()
|
|
|
|
{
|
|
|
|
return Err(StoreError::DoubleStore);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(stored_profile.into())
|
|
|
|
}
|
|
|
|
|
2021-01-07 05:39:55 +00:00
|
|
|
pub fn for_local(&self, id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
self.for_owner(OwnerSource::Local(id))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn for_owner(&self, owner_source: OwnerSource) -> impl DoubleEndedIterator<Item = Uuid> {
|
2021-01-27 02:29:18 +00:00
|
|
|
self.owner_created_tree
|
|
|
|
.scan_prefix(owner_created_profile_prefix(&owner_source))
|
2021-01-04 17:34:31 +00:00
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
2021-01-26 02:37:36 +00:00
|
|
|
.filter_map(|ivec| Uuid::from_slice(&ivec).ok())
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn date_range<K>(
|
|
|
|
&self,
|
|
|
|
range: impl std::ops::RangeBounds<K>,
|
|
|
|
) -> impl DoubleEndedIterator<Item = Uuid>
|
|
|
|
where
|
|
|
|
K: AsRef<[u8]>,
|
|
|
|
{
|
|
|
|
self.owner_created_tree
|
|
|
|
.range(range)
|
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
2021-01-26 02:37:36 +00:00
|
|
|
.filter_map(move |ivec| Uuid::from_slice(&ivec).ok())
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-25 04:07:11 +00:00
|
|
|
pub fn all(&self) -> impl DoubleEndedIterator<Item = Uuid> {
|
2021-01-27 02:32:03 +00:00
|
|
|
self.created_tree
|
2021-01-25 04:07:11 +00:00
|
|
|
.iter()
|
|
|
|
.values()
|
|
|
|
.filter_map(|res| res.ok())
|
2021-01-26 02:37:36 +00:00
|
|
|
.filter_map(|ivec| Uuid::from_slice(&ivec).ok())
|
2021-01-25 04:07:11 +00:00
|
|
|
.rev()
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
pub fn newer_than(&self, id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
let this = self.clone();
|
|
|
|
|
|
|
|
self.profile_tree
|
|
|
|
.get(id_profile_key(id))
|
|
|
|
.ok()
|
|
|
|
.and_then(|opt| opt)
|
|
|
|
.and_then(|stored_profile_ivec| {
|
|
|
|
let stored_profile: StoredProfile =
|
|
|
|
serde_json::from_slice(&stored_profile_ivec).ok()?;
|
|
|
|
|
|
|
|
Some((
|
|
|
|
stored_profile.owner_source.into(),
|
|
|
|
stored_profile.created_at,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(move |(source, created_at)| {
|
|
|
|
let range_start = owner_created_profile_range_start(&source, created_at);
|
|
|
|
let range_start = range_start.as_bytes().to_vec();
|
|
|
|
this.date_range(range_start..)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn older_than(&self, id: Uuid) -> impl DoubleEndedIterator<Item = Uuid> {
|
|
|
|
let this = self.clone();
|
|
|
|
|
|
|
|
self.profile_tree
|
|
|
|
.get(id_profile_key(id))
|
|
|
|
.ok()
|
|
|
|
.and_then(|opt| opt)
|
|
|
|
.and_then(|stored_profile_ivec| {
|
|
|
|
let stored_profile: StoredProfile =
|
|
|
|
serde_json::from_slice(&stored_profile_ivec).ok()?;
|
|
|
|
|
|
|
|
Some((
|
|
|
|
stored_profile.owner_source.into(),
|
|
|
|
stored_profile.created_at,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(move |(source, created_at)| {
|
|
|
|
let range_end = owner_created_profile_range_start(&source, created_at);
|
|
|
|
let range_end = range_end.as_bytes().to_vec();
|
|
|
|
this.date_range(..range_end)
|
|
|
|
})
|
2021-01-24 04:03:39 +00:00
|
|
|
.rev()
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_local(&self, id: Uuid) -> Result<Option<bool>, StoreError> {
|
|
|
|
if let Some(profile) = self.by_id(id)? {
|
|
|
|
return Ok(Some(profile.owner_source.is_local()));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn by_id(&self, id: Uuid) -> Result<Option<Profile>, StoreError> {
|
|
|
|
let stored_profile_ivec = match self.profile_tree.get(id_profile_key(id).as_bytes())? {
|
|
|
|
Some(ivec) => ivec,
|
|
|
|
None => return Ok(None),
|
|
|
|
};
|
|
|
|
|
|
|
|
let stored_profile: StoredProfile = serde_json::from_slice(&stored_profile_ivec)?;
|
|
|
|
|
|
|
|
Ok(Some(stored_profile.into()))
|
|
|
|
}
|
|
|
|
|
2021-01-09 04:34:13 +00:00
|
|
|
pub fn by_handle(&self, handle: &str, domain: &str) -> Result<Option<Uuid>, StoreError> {
|
2021-01-04 17:34:31 +00:00
|
|
|
let id_ivec = match self.handle_tree.get(handle_id_key(handle, domain))? {
|
|
|
|
Some(ivec) => ivec,
|
|
|
|
None => return Ok(None),
|
|
|
|
};
|
|
|
|
|
2021-01-26 02:37:36 +00:00
|
|
|
Ok(Uuid::from_slice(&id_ivec).ok())
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-14 04:43:43 +00:00
|
|
|
pub(crate) fn suspend(&self, profile_id: Uuid) -> Result<Option<Undo<Profile>>, StoreError> {
|
|
|
|
let stored_profile_ivec = match self
|
|
|
|
.profile_tree
|
|
|
|
.get(id_profile_key(profile_id).as_bytes())?
|
|
|
|
{
|
|
|
|
Some(ivec) => ivec,
|
|
|
|
None => return Ok(None),
|
|
|
|
};
|
|
|
|
|
2021-01-15 02:41:53 +00:00
|
|
|
let now = Utc::now();
|
2021-01-14 04:43:43 +00:00
|
|
|
let mut stored_profile: StoredProfile = serde_json::from_slice(&stored_profile_ivec)?;
|
2021-01-15 02:41:53 +00:00
|
|
|
stored_profile.banner = None;
|
|
|
|
stored_profile.icon = None;
|
|
|
|
stored_profile.description = None;
|
|
|
|
stored_profile.display_name = None;
|
|
|
|
stored_profile.suspended_at = Some(now);
|
2021-02-09 00:23:09 +00:00
|
|
|
stored_profile.updated = Some(now);
|
2021-01-14 04:43:43 +00:00
|
|
|
let stored_profile_vec = serde_json::to_vec(&stored_profile)?;
|
|
|
|
|
2021-02-09 00:49:14 +00:00
|
|
|
if let Err(e) = self.handle_index.remove(&stored_profile.handle) {
|
|
|
|
log::error!(
|
|
|
|
"Failed to remove {} from handle index: {}",
|
|
|
|
stored_profile.handle,
|
|
|
|
e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-01-14 04:43:43 +00:00
|
|
|
if self
|
|
|
|
.profile_tree
|
|
|
|
.compare_and_swap(
|
|
|
|
id_profile_key(profile_id).as_bytes(),
|
|
|
|
Some(&stored_profile_ivec),
|
|
|
|
Some(stored_profile_vec),
|
|
|
|
)?
|
|
|
|
.is_err()
|
|
|
|
{
|
|
|
|
return Err(StoreError::DoubleStore);
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
Ok(Some(Undo(stored_profile.into())))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to map id -> Profile
|
|
|
|
fn id_profile_key(id: Uuid) -> String {
|
|
|
|
format!("/profile/{}/data", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to map handle -> id
|
|
|
|
fn handle_id_key(handle: &str, domain: &str) -> String {
|
|
|
|
format!("/handle/{}:{}/profile", handle, domain)
|
|
|
|
}
|
|
|
|
|
2021-01-26 02:37:36 +00:00
|
|
|
fn handle_id_prefix(handle: &str) -> String {
|
|
|
|
format!("/handle/{}:", handle)
|
|
|
|
}
|
|
|
|
|
2021-01-27 02:29:18 +00:00
|
|
|
fn owner_created_profile_prefix(owner: &OwnerSource) -> String {
|
|
|
|
format!("/owner/{}/created", owner)
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Used to fetch profiles for a given owner in a user-recognizalbe order
|
|
|
|
fn owner_created_profile_key(owner: &OwnerSource, created_at: DateTime<Utc>, id: Uuid) -> String {
|
|
|
|
format!(
|
|
|
|
"/owner/{}/created/{}/profile/{}",
|
|
|
|
owner,
|
|
|
|
created_at.to_rfc3339(),
|
|
|
|
id
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn owner_created_profile_range_start(owner: &OwnerSource, created_at: DateTime<Utc>) -> String {
|
|
|
|
format!("/owner/{}/created/{}", owner, created_at.to_rfc3339(),)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to map created_at -> id
|
|
|
|
fn created_profile_key(created_at: DateTime<Utc>, id: Uuid) -> String {
|
|
|
|
format!("/created/{}/profile/{}", created_at, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn owner_profile_count_key(owner: &OwnerSource) -> String {
|
|
|
|
format!("/owner/{}/count", owner)
|
|
|
|
}
|
|
|
|
|
2021-01-12 03:58:03 +00:00
|
|
|
impl From<StoredOwnerSource> for OwnerSource {
|
|
|
|
fn from(os: StoredOwnerSource) -> Self {
|
2021-01-04 17:34:31 +00:00
|
|
|
match os {
|
|
|
|
StoredOwnerSource::Local(id) => OwnerSource::Local(id),
|
2021-01-16 04:25:06 +00:00
|
|
|
StoredOwnerSource::Remote(id) => OwnerSource::Remote(id),
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-28 02:58:38 +00:00
|
|
|
impl From<StoredProfile> for Profile {
|
|
|
|
fn from(sp: StoredProfile) -> Self {
|
2021-01-04 17:34:31 +00:00
|
|
|
Profile {
|
|
|
|
id: sp.id,
|
|
|
|
owner_source: sp.owner_source.into(),
|
|
|
|
handle: sp.handle.to_owned(),
|
|
|
|
domain: sp.domain.to_owned(),
|
2021-01-12 03:58:03 +00:00
|
|
|
display_name: sp.display_name,
|
2021-01-28 02:58:38 +00:00
|
|
|
display_name_source: sp.display_name_source,
|
2021-01-12 03:58:03 +00:00
|
|
|
description: sp.description,
|
2021-01-28 02:58:38 +00:00
|
|
|
description_source: sp.description_source,
|
2021-01-04 17:34:31 +00:00
|
|
|
icon: sp.icon,
|
|
|
|
banner: sp.banner,
|
|
|
|
published: sp.published,
|
2021-02-09 00:23:09 +00:00
|
|
|
updated: sp.updated,
|
2021-01-04 17:34:31 +00:00
|
|
|
login_required: sp.login_required,
|
2021-01-14 04:43:43 +00:00
|
|
|
suspended: sp.suspended_at.is_some(),
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|