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 { 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, ) -> Result { 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 all(&self) -> impl DoubleEndedIterator { self.created_open_report .iter() .values() .filter_map(|res| res.ok()) .filter_map(uuid_from_ivec) } pub fn by_id(&self, id: Uuid) -> Result { let reporter_kind = self .report_reporter_kind .get(id.as_bytes())? .and_then(reporter_kind_from_ivec) .ok_or_else(|| StoreError::Missing)?; let reporter_opt = match reporter_kind { ReporterKind::Profile => self.report_profile.get(id.as_bytes())?, ReporterKind::Server => self.report_server.get(id.as_bytes())?, }; let reporter = reporter_opt .and_then(uuid_from_ivec) .ok_or_else(|| StoreError::Missing)?; let kind = self .report_kind .get(id.as_bytes())? .and_then(kind_from_ivec) .ok_or_else(|| StoreError::Missing)?; let reported_item_opt = match kind { ReportKind::Profile => self.report_reported_profile.get(id.as_bytes())?, ReportKind::Submission => self.report_reported_submission.get(id.as_bytes())?, ReportKind::Comment => self.report_reported_comment.get(id.as_bytes())?, ReportKind::Post => self.report_reported_post.get(id.as_bytes())?, }; let reported_item = reported_item_opt .and_then(uuid_from_ivec) .ok_or_else(|| StoreError::Missing)?; 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); Ok(Report { id, reporter, reporter_kind, reported_item, kind, note, resolved, resolution, forwarded, }) } pub(crate) fn forwarded(&self, report_id: Uuid) -> Result>, StoreError> { Ok(self .report_forwarded .get(report_id.as_bytes())? .and_then(date_from_ivec)) } pub(crate) fn resolved(&self, report_id: Uuid) -> Result>, StoreError> { Ok(self .report_resolved .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 { serde_json::from_slice(&ivec).ok() } fn reporter_kind_from_ivec(ivec: sled::IVec) -> Option { serde_json::from_slice(&ivec).ok() } fn date_from_ivec(ivec: sled::IVec) -> Option> { String::from_utf8_lossy(&ivec).parse().ok() } fn uuid_from_ivec(ivec: sled::IVec) -> Option { 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, report_id: Uuid) -> String { format!("/created/{}/report/{}", created_at.to_rfc3339(), report_id) } fn resolved_resolved_reports_key(resolved_at: DateTime, report_id: Uuid) -> String { format!( "/resolved/{}/report/{}", resolved_at.to_rfc3339(), report_id ) }