hyaenidae/profiles/src/store/report.rs

481 lines
16 KiB
Rust

use crate::store::{Report, ReportKind, ReportState, ReporterKind, StoreError};
use chrono::{DateTime, Utc};
use sled::{Db, Transactional, Tree};
use uuid::Uuid;
#[derive(Clone)]
pub struct Store {
server_open_report: Tree,
profile_open_report: Tree,
created_open_report: Tree,
resolved_resolved_report: Tree,
report_state: Tree,
report_reporter_kind: Tree,
report_profile: Tree,
report_server: Tree,
report_kind: Tree,
report_reported_post: Tree,
report_reported_comment: Tree,
report_reported_submission: Tree,
report_reported_profile: Tree,
report_note: Tree,
report_created: Tree,
report_resolved: Tree,
report_resolution: Tree,
report_forwarded: Tree,
}
impl Store {
pub(super) fn build(db: &Db) -> Result<Self, sled::Error> {
Ok(Store {
server_open_report: db.open_tree("/profiles/reports/server_open_report")?,
profile_open_report: db.open_tree("/profiles/reports/profile_open_report")?,
created_open_report: db.open_tree("/profiles/reports/created_open_report")?,
resolved_resolved_report: db.open_tree("/profiles/reports/resolved_resolved_report")?,
report_state: db.open_tree("/profiles/reports/report_state")?,
report_reporter_kind: db.open_tree("/profiles/reports/report_reporter_kind")?,
report_profile: db.open_tree("/profiles/reports/report_profile")?,
report_server: db.open_tree("/profiles/reports/report_server")?,
report_kind: db.open_tree("/profiles/reports/report_kind")?,
report_reported_post: db.open_tree("/profiles/reports/report_reported_post")?,
report_reported_comment: db.open_tree("/profiles/reports/report_reported_comment")?,
report_reported_submission: db
.open_tree("/profiles/reports/report_reported_submission")?,
report_reported_profile: db.open_tree("/profiles/reports/report_reported_profile")?,
report_note: db.open_tree("/profiles/reports/report_note")?,
report_created: db.open_tree("/profiles/reports/report_created")?,
report_resolved: db.open_tree("/profiles/reports/report_resolved")?,
report_resolution: db.open_tree("/profiles/reports/report_resolution")?,
report_forwarded: db.open_tree("/profiles/reports/report_forwarded")?,
})
}
pub(crate) fn report(
&self,
reported_item: Uuid,
kind: ReportKind,
reporter: Uuid,
reporter_kind: ReporterKind,
note: Option<String>,
) -> Result<Report, StoreError> {
let mut id: Uuid;
let created = Utc::now();
let state = ReportState::Open;
let open_vec = serde_json::to_vec(&state)?;
let kind_vec = serde_json::to_vec(&kind)?;
let reporter_kind_vec = serde_json::to_vec(&reporter_kind)?;
while {
id = Uuid::new_v4();
self.report_state
.compare_and_swap(
id.as_bytes(),
None as Option<&[u8]>,
Some(open_vec.as_slice()),
)?
.is_err()
} {}
let report = Report {
id,
reporter,
reporter_kind,
reported_item,
kind,
note: note.clone(),
resolved: None,
resolution: None,
forwarded: None,
};
let res = [
&self.server_open_report,
&self.profile_open_report,
&self.created_open_report,
&self.report_reporter_kind,
&self.report_profile,
&self.report_server,
&self.report_kind,
&self.report_reported_post,
&self.report_reported_comment,
&self.report_reported_submission,
&self.report_reported_profile,
&self.report_note,
&self.report_created,
]
.transaction(move |trees| {
let server_open_report = &trees[0];
let profile_open_report = &trees[1];
let created_open_report = &trees[2];
let report_reporter_kind = &trees[3];
let report_profile = &trees[4];
let report_server = &trees[5];
let report_kind = &trees[6];
let report_reported_post = &trees[7];
let report_reported_comment = &trees[8];
let report_reported_submission = &trees[9];
let report_reported_profile = &trees[10];
let report_note = &trees[11];
let report_created = &trees[12];
report_reporter_kind.insert(id.as_bytes(), reporter_kind_vec.as_slice())?;
match reporter_kind {
ReporterKind::Server => {
server_open_report.insert(
server_open_reports_key(reporter, id).as_bytes(),
id.as_bytes(),
)?;
report_server.insert(id.as_bytes(), reporter.as_bytes())?;
}
ReporterKind::Profile => {
profile_open_report.insert(
profile_open_reports_key(reporter, id).as_bytes(),
id.as_bytes(),
)?;
report_profile.insert(id.as_bytes(), reporter.as_bytes())?;
}
}
created_open_report.insert(
created_open_reports_key(created, id).as_bytes(),
id.as_bytes(),
)?;
report_kind.insert(id.as_bytes(), kind_vec.as_slice())?;
match kind {
ReportKind::Post => {
report_reported_post.insert(id.as_bytes(), reported_item.as_bytes())?;
}
ReportKind::Submission => {
report_reported_submission.insert(id.as_bytes(), reported_item.as_bytes())?;
}
ReportKind::Comment => {
report_reported_comment.insert(id.as_bytes(), reported_item.as_bytes())?;
}
ReportKind::Profile => {
report_reported_profile.insert(id.as_bytes(), reported_item.as_bytes())?;
}
}
if let Some(note) = &note {
report_note.insert(id.as_bytes(), note.as_bytes())?;
}
report_created.insert(id.as_bytes(), created.to_rfc3339().as_bytes())?;
Ok(())
});
if let Err(e) = res {
self.report_state.remove(id.as_bytes())?;
return Err(e.into());
}
Ok(report)
}
pub fn open_reports(&self) -> impl DoubleEndedIterator<Item = Uuid> {
let start: &[u8] = &[];
self.open_reports_date_range(start..).rev()
}
pub fn closed_reports(&self) -> impl DoubleEndedIterator<Item = Uuid> {
let start: &[u8] = &[];
self.closed_reports_date_range(start..).rev()
}
pub fn open_reports_newer_than<'a>(
&'a self,
id: Uuid,
) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
self.report_created
.get(id.as_bytes())
.ok()
.and_then(|opt| opt)
.and_then(date_from_ivec)
.into_iter()
.flat_map(move |created| {
self.open_reports_date_range(created_open_reports_bound(created)..)
})
}
pub fn open_reports_older_than<'a>(
&'a self,
id: Uuid,
) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
self.report_created
.get(id.as_bytes())
.ok()
.and_then(|opt| opt)
.and_then(date_from_ivec)
.into_iter()
.flat_map(move |created| {
self.open_reports_date_range(..created_open_reports_bound(created))
})
.rev()
}
pub fn closed_reports_newer_than<'a>(
&'a self,
id: Uuid,
) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
self.report_resolved
.get(id.as_bytes())
.ok()
.and_then(|opt| opt)
.and_then(date_from_ivec)
.into_iter()
.flat_map(move |resolved| {
self.closed_reports_date_range(resolved_resolved_reports_bound(resolved)..)
})
}
pub fn closed_reports_older_than<'a>(
&'a self,
id: Uuid,
) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
self.report_resolved
.get(id.as_bytes())
.ok()
.and_then(|opt| opt)
.and_then(date_from_ivec)
.into_iter()
.flat_map(move |resolved| {
self.closed_reports_date_range(..resolved_resolved_reports_bound(resolved))
})
.rev()
}
fn closed_reports_date_range<K>(
&self,
range: impl std::ops::RangeBounds<K>,
) -> impl DoubleEndedIterator<Item = Uuid>
where
K: AsRef<[u8]>,
{
self.resolved_resolved_report
.range(range)
.values()
.filter_map(|res| res.ok())
.filter_map(uuid_from_ivec)
}
fn open_reports_date_range<K>(
&self,
range: impl std::ops::RangeBounds<K>,
) -> impl DoubleEndedIterator<Item = Uuid>
where
K: AsRef<[u8]>,
{
self.created_open_report
.range(range)
.values()
.filter_map(|res| res.ok())
.filter_map(uuid_from_ivec)
}
pub fn by_id(&self, id: Uuid) -> Result<Option<Report>, StoreError> {
let reporter_kind = self
.report_reporter_kind
.get(id.as_bytes())?
.and_then(reporter_kind_from_ivec);
let reporter_opt = match reporter_kind {
Some(ReporterKind::Profile) => self.report_profile.get(id.as_bytes())?,
Some(ReporterKind::Server) => self.report_server.get(id.as_bytes())?,
None => None,
};
let reporter = reporter_opt.and_then(uuid_from_ivec);
let kind = self
.report_kind
.get(id.as_bytes())?
.and_then(kind_from_ivec);
let reported_item_opt = match kind {
Some(ReportKind::Profile) => self.report_reported_profile.get(id.as_bytes())?,
Some(ReportKind::Submission) => self.report_reported_submission.get(id.as_bytes())?,
Some(ReportKind::Comment) => self.report_reported_comment.get(id.as_bytes())?,
Some(ReportKind::Post) => self.report_reported_post.get(id.as_bytes())?,
None => None,
};
let reported_item = reported_item_opt.and_then(uuid_from_ivec);
let note = self.report_note.get(id.as_bytes())?.map(string_from_ivec);
let resolved = self
.report_resolved
.get(id.as_bytes())?
.and_then(date_from_ivec);
let resolution = self
.report_resolution
.get(id.as_bytes())?
.map(string_from_ivec);
let forwarded = self
.report_forwarded
.get(id.as_bytes())?
.and_then(date_from_ivec);
let optionable = move || -> Option<Report> {
Some(Report {
id,
reporter: reporter?,
reporter_kind: reporter_kind?,
reported_item: reported_item?,
kind: kind?,
note,
resolved,
resolution,
forwarded,
})
};
Ok((optionable)())
}
pub(crate) fn forwarded(&self, report_id: Uuid) -> Result<Option<DateTime<Utc>>, StoreError> {
Ok(self
.report_forwarded
.get(report_id.as_bytes())?
.and_then(date_from_ivec))
}
pub(crate) fn forward_report(&self, report_id: Uuid) -> Result<(), StoreError> {
let forwarded = Utc::now();
self.report_forwarded
.insert(report_id.as_bytes(), forwarded.to_rfc3339().as_bytes())?;
Ok(())
}
pub(crate) fn resolve_report(
&self,
report_id: Uuid,
resolution: String,
) -> Result<(), StoreError> {
let state = ReportState::Resolved;
let state_vec = serde_json::to_vec(&state)?;
let created = self
.report_created
.get(report_id.as_bytes())?
.and_then(date_from_ivec)
.ok_or_else(|| StoreError::Missing)?;
let reporter_kind = self
.report_reporter_kind
.get(report_id.as_bytes())?
.and_then(reporter_kind_from_ivec)
.ok_or_else(|| StoreError::Missing)?;
let profile = if matches!(reporter_kind, ReporterKind::Profile) {
self.report_profile
.get(report_id.as_bytes())?
.and_then(uuid_from_ivec)
} else {
None
};
let server = if matches!(reporter_kind, ReporterKind::Server) {
self.report_server
.get(report_id.as_bytes())?
.and_then(uuid_from_ivec)
} else {
None
};
let resolved = Utc::now();
[
&self.server_open_report,
&self.profile_open_report,
&self.created_open_report,
&self.resolved_resolved_report,
&self.report_state,
&self.report_resolved,
&self.report_resolution,
]
.transaction(move |trees| {
let server_open_report = &trees[0];
let profile_open_report = &trees[1];
let created_open_report = &trees[2];
let resolved_resolved_report = &trees[3];
let report_state = &trees[4];
let report_resolved = &trees[5];
let report_resolution = &trees[6];
if let Some(server) = server {
server_open_report.remove(server_open_reports_key(server, report_id).as_bytes())?;
}
if let Some(profile) = profile {
profile_open_report
.remove(profile_open_reports_key(profile, report_id).as_bytes())?;
}
created_open_report.remove(created_open_reports_key(created, report_id).as_bytes())?;
resolved_resolved_report.insert(
resolved_resolved_reports_key(resolved, report_id).as_bytes(),
report_id.as_bytes(),
)?;
report_state.insert(report_id.as_bytes(), state_vec.as_slice())?;
report_resolved.insert(report_id.as_bytes(), resolved.to_rfc3339().as_bytes())?;
report_resolution.insert(report_id.as_bytes(), resolution.as_bytes())?;
Ok(())
})?;
Ok(())
}
}
fn kind_from_ivec(ivec: sled::IVec) -> Option<ReportKind> {
serde_json::from_slice(&ivec).ok()
}
fn reporter_kind_from_ivec(ivec: sled::IVec) -> Option<ReporterKind> {
serde_json::from_slice(&ivec).ok()
}
fn date_from_ivec(ivec: sled::IVec) -> Option<DateTime<Utc>> {
String::from_utf8_lossy(&ivec).parse().ok()
}
fn uuid_from_ivec(ivec: sled::IVec) -> Option<Uuid> {
Uuid::from_slice(&ivec).ok()
}
fn string_from_ivec(ivec: sled::IVec) -> String {
String::from_utf8_lossy(&ivec).to_string()
}
fn profile_open_reports_key(profile_id: Uuid, report_id: Uuid) -> String {
format!("/profile/{}/report/{}", profile_id, report_id)
}
fn server_open_reports_key(server_id: Uuid, report_id: Uuid) -> String {
format!("/server/{}/report/{}", server_id, report_id)
}
fn created_open_reports_key(created_at: DateTime<Utc>, report_id: Uuid) -> String {
format!("/created/{}/report/{}", created_at.to_rfc3339(), report_id)
}
fn created_open_reports_bound(created_at: DateTime<Utc>) -> String {
format!("/created/{}/report/", created_at.to_rfc3339())
}
fn resolved_resolved_reports_key(resolved_at: DateTime<Utc>, report_id: Uuid) -> String {
format!(
"/resolved/{}/report/{}",
resolved_at.to_rfc3339(),
report_id
)
}
fn resolved_resolved_reports_bound(resolved_at: DateTime<Utc>) -> String {
format!("/resolved/{}/report/", resolved_at.to_rfc3339())
}