481 lines
16 KiB
Rust
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) = ¬e {
|
|
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())
|
|
}
|