945 lines
22 KiB
Rust
945 lines
22 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use sled::Db;
|
|
use std::fmt;
|
|
use uuid::Uuid;
|
|
|
|
mod comment;
|
|
mod file;
|
|
mod profile;
|
|
mod react;
|
|
mod report;
|
|
mod server;
|
|
mod submission;
|
|
mod term_search;
|
|
pub mod view;
|
|
|
|
pub use comment::Store as CommentStore;
|
|
pub use file::Store as FileStore;
|
|
pub use profile::Store as ProfileStore;
|
|
pub use react::Store as ReactStore;
|
|
pub use report::Store as ReportStore;
|
|
pub use server::{Server, ServerChanges, Store as ServerStore};
|
|
pub use submission::Store as SubmissionStore;
|
|
pub use term_search::TermSearch;
|
|
pub use view::Store as ViewStore;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Store {
|
|
pub profiles: profile::Store,
|
|
pub files: file::Store,
|
|
pub submissions: submission::Store,
|
|
pub comments: comment::Store,
|
|
pub reacts: react::Store,
|
|
pub reports: report::Store,
|
|
pub servers: server::Store,
|
|
pub view: view::Store,
|
|
}
|
|
|
|
impl Store {
|
|
pub fn build(db: &Db) -> Result<Store, sled::Error> {
|
|
Ok(Store {
|
|
profiles: profile::Store::build(db)?,
|
|
files: file::Store::build(db)?,
|
|
submissions: submission::Store::build(db)?,
|
|
comments: comment::Store::build(db)?,
|
|
reacts: react::Store::build(db)?,
|
|
reports: report::Store::build(db)?,
|
|
servers: server::Store::build(db)?,
|
|
view: view::Store::build(db)?,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn is_domain_blocked(&self, domain: &str) -> Result<bool, StoreError> {
|
|
if let Some(id) = self.servers.by_domain(domain)? {
|
|
return self.is_blocked(id);
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
pub(crate) fn is_domain_federated(&self, domain: &str) -> Result<bool, StoreError> {
|
|
if let Some(id) = self.servers.by_domain(domain)? {
|
|
return self.is_federated(id);
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
pub(crate) fn is_blocked(&self, id: Uuid) -> Result<bool, StoreError> {
|
|
if let Some(self_id) = self.servers.get_self()? {
|
|
let forward = self.view.server_blocks.by_forward(id, self_id)?.is_some();
|
|
let backward = self.view.server_blocks.by_forward(self_id, id)?.is_some();
|
|
|
|
return Ok(forward || backward);
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
pub(crate) fn is_federated(&self, id: Uuid) -> Result<bool, StoreError> {
|
|
if let Some(self_id) = self.servers.get_self()? {
|
|
let forward = self.view.server_follows.by_forward(id, self_id)?.is_some();
|
|
let backward = self.view.server_follows.by_forward(self_id, id)?.is_some();
|
|
|
|
return Ok(forward || backward);
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
pub(crate) fn followers_for<'a>(
|
|
&'a self,
|
|
profile_id: Uuid,
|
|
) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
|
|
self.view.follows.forward_iter(profile_id)
|
|
}
|
|
|
|
pub(crate) fn federated_servers<'a>(&'a self) -> impl DoubleEndedIterator<Item = Uuid> + 'a {
|
|
self.servers
|
|
.get_self()
|
|
.ok()
|
|
.and_then(|opt| opt)
|
|
.into_iter()
|
|
.flat_map(move |server_id| {
|
|
let iter1 = self.view.server_follows.forward_iter(server_id);
|
|
let iter2 = self.view.server_follows.backward_iter(server_id);
|
|
|
|
iter1.chain(iter2)
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Report {
|
|
id: Uuid,
|
|
reporter: Uuid,
|
|
reporter_kind: ReporterKind,
|
|
reported_item: Uuid,
|
|
kind: ReportKind,
|
|
note: Option<String>,
|
|
resolved: Option<DateTime<Utc>>,
|
|
resolution: Option<String>,
|
|
forwarded: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
impl Report {
|
|
pub fn id(&self) -> Uuid {
|
|
self.id
|
|
}
|
|
|
|
pub fn note(&self) -> Option<&str> {
|
|
self.note.as_deref()
|
|
}
|
|
|
|
pub fn reporter_profile(&self) -> Option<Uuid> {
|
|
if matches!(self.reporter_kind, ReporterKind::Profile) {
|
|
Some(self.reporter)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn item(&self) -> Uuid {
|
|
self.reported_item
|
|
}
|
|
|
|
pub fn reporter_server(&self) -> Option<Uuid> {
|
|
if matches!(self.reporter_kind, ReporterKind::Server) {
|
|
Some(self.reporter)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn kind(&self) -> ReportKind {
|
|
self.kind
|
|
}
|
|
|
|
pub fn profile(&self) -> Option<Uuid> {
|
|
if matches!(self.kind, ReportKind::Profile) {
|
|
Some(self.reported_item)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn submission(&self) -> Option<Uuid> {
|
|
if matches!(self.kind, ReportKind::Submission) {
|
|
Some(self.reported_item)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn comment(&self) -> Option<Uuid> {
|
|
if matches!(self.kind, ReportKind::Comment) {
|
|
Some(self.reported_item)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn post(&self) -> Option<Uuid> {
|
|
if matches!(self.kind, ReportKind::Post) {
|
|
Some(self.reported_item)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn resolved(&self) -> Option<DateTime<Utc>> {
|
|
self.resolved
|
|
}
|
|
|
|
pub fn resolution(&self) -> Option<&str> {
|
|
self.resolution.as_deref()
|
|
}
|
|
|
|
pub fn forwarded(&self) -> Option<DateTime<Utc>> {
|
|
self.forwarded
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
|
|
pub enum ReportKind {
|
|
Post,
|
|
Comment,
|
|
Submission,
|
|
Profile,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
|
|
pub(crate) enum ReporterKind {
|
|
Profile,
|
|
Server,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
|
|
enum ReportState {
|
|
Open,
|
|
Resolved,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum OwnerSource {
|
|
Local(Uuid),
|
|
Remote(Uuid),
|
|
}
|
|
|
|
impl OwnerSource {
|
|
pub fn is_local(&self) -> bool {
|
|
matches!(self, OwnerSource::Local(_))
|
|
}
|
|
}
|
|
|
|
#[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>,
|
|
login_required: bool,
|
|
suspended: bool,
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
pub fn update_images(&self) -> ProfileImageChanges {
|
|
ProfileImageChanges {
|
|
id: self.id,
|
|
icon: None,
|
|
banner: None,
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
pub fn login_required(&self) -> bool {
|
|
self.login_required
|
|
}
|
|
|
|
pub fn is_suspended(&self) -> bool {
|
|
self.suspended
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct PictRsFile {
|
|
key: String,
|
|
token: String,
|
|
width: usize,
|
|
height: usize,
|
|
media_type: mime::Mime,
|
|
}
|
|
|
|
impl PictRsFile {
|
|
pub(crate) fn new(
|
|
key: &str,
|
|
token: &str,
|
|
width: usize,
|
|
height: usize,
|
|
media_type: mime::Mime,
|
|
) -> Self {
|
|
PictRsFile {
|
|
key: key.to_owned(),
|
|
token: token.to_owned(),
|
|
width,
|
|
height,
|
|
media_type,
|
|
}
|
|
}
|
|
|
|
pub fn key(&self) -> &str {
|
|
&self.key
|
|
}
|
|
|
|
pub(crate) fn token(&self) -> &str {
|
|
&self.token
|
|
}
|
|
|
|
pub(crate) fn media_type(&self) -> &mime::Mime {
|
|
&self.media_type
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum FileSource {
|
|
PictRs(PictRsFile),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct File {
|
|
id: Uuid,
|
|
source: FileSource,
|
|
}
|
|
|
|
impl File {
|
|
pub fn id(&self) -> Uuid {
|
|
self.id
|
|
}
|
|
|
|
pub fn source(&self) -> &FileSource {
|
|
&self.source
|
|
}
|
|
|
|
pub fn pictrs(&self) -> Option<&PictRsFile> {
|
|
let FileSource::PictRs(ref file) = self.source;
|
|
Some(file)
|
|
}
|
|
|
|
pub fn pictrs_key(&self) -> Option<&str> {
|
|
self.pictrs().map(|p| p.key())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
|
|
pub enum Visibility {
|
|
Public,
|
|
Unlisted,
|
|
Followers,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Submission {
|
|
id: Uuid,
|
|
profile_id: Uuid,
|
|
title: String,
|
|
title_source: Option<String>,
|
|
description: Option<String>,
|
|
description_source: Option<String>,
|
|
files: Vec<Uuid>,
|
|
published: Option<DateTime<Utc>>,
|
|
visibility: Visibility,
|
|
}
|
|
|
|
impl Submission {
|
|
pub fn update(&self) -> SubmissionChanges {
|
|
SubmissionChanges {
|
|
id: self.id,
|
|
title: None,
|
|
title_source: None,
|
|
description: None,
|
|
description_source: None,
|
|
visibility: None,
|
|
published: self.published,
|
|
}
|
|
}
|
|
|
|
pub fn update_files(&self) -> SubmissionFileChanges {
|
|
SubmissionFileChanges {
|
|
id: self.id,
|
|
original_files: self.files.clone(),
|
|
files: self.files.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn id(&self) -> Uuid {
|
|
self.id
|
|
}
|
|
|
|
pub fn profile_id(&self) -> Uuid {
|
|
self.profile_id
|
|
}
|
|
|
|
pub fn title(&self) -> &str {
|
|
&self.title
|
|
}
|
|
|
|
pub fn title_source(&self) -> Option<&str> {
|
|
self.title_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 files(&self) -> &[Uuid] {
|
|
&self.files
|
|
}
|
|
|
|
pub fn published(&self) -> Option<DateTime<Utc>> {
|
|
self.published
|
|
}
|
|
|
|
pub fn visibility(&self) -> Visibility {
|
|
self.visibility
|
|
}
|
|
|
|
pub fn is_public(&self) -> bool {
|
|
matches!(self.visibility, Visibility::Public)
|
|
}
|
|
|
|
pub fn is_unlisted(&self) -> bool {
|
|
matches!(self.visibility, Visibility::Unlisted)
|
|
}
|
|
|
|
pub fn is_followers_only(&self) -> bool {
|
|
matches!(self.visibility, Visibility::Followers)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Comment {
|
|
Submission(SubmissionComment),
|
|
Reply(ReplyComment),
|
|
}
|
|
|
|
impl Comment {
|
|
pub fn id(&self) -> Uuid {
|
|
match self {
|
|
Comment::Reply(ReplyComment { id, .. }) => *id,
|
|
Comment::Submission(SubmissionComment { id, .. }) => *id,
|
|
}
|
|
}
|
|
|
|
pub fn submission_id(&self) -> Uuid {
|
|
match self {
|
|
Comment::Reply(ReplyComment { submission_id, .. }) => *submission_id,
|
|
Comment::Submission(SubmissionComment { submission_id, .. }) => *submission_id,
|
|
}
|
|
}
|
|
|
|
pub fn profile_id(&self) -> Uuid {
|
|
match self {
|
|
Comment::Reply(ReplyComment { profile_id, .. }) => *profile_id,
|
|
Comment::Submission(SubmissionComment { profile_id, .. }) => *profile_id,
|
|
}
|
|
}
|
|
|
|
pub fn comment_id(&self) -> Option<Uuid> {
|
|
match self {
|
|
Comment::Reply(ReplyComment { comment_id, .. }) => Some(*comment_id),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn body(&self) -> &str {
|
|
if !self.deleted() {
|
|
match self {
|
|
Comment::Reply(ReplyComment { body, .. }) => &body,
|
|
Comment::Submission(SubmissionComment { body, .. }) => &body,
|
|
}
|
|
} else {
|
|
"Comment Deleted"
|
|
}
|
|
}
|
|
|
|
pub fn body_source(&self) -> Option<&str> {
|
|
match self {
|
|
Comment::Reply(ReplyComment { body_source, .. }) => body_source.as_deref(),
|
|
Comment::Submission(SubmissionComment { body_source, .. }) => body_source.as_deref(),
|
|
}
|
|
}
|
|
|
|
pub fn published(&self) -> DateTime<Utc> {
|
|
match self {
|
|
Comment::Reply(ReplyComment { published, .. }) => *published,
|
|
Comment::Submission(SubmissionComment { published, .. }) => *published,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn update(&self) -> CommentChanges {
|
|
match self {
|
|
Comment::Reply(rc) => rc.update(),
|
|
Comment::Submission(sc) => sc.update(),
|
|
}
|
|
}
|
|
|
|
pub fn deleted(&self) -> bool {
|
|
match self {
|
|
Comment::Reply(rc) => rc.deleted,
|
|
Comment::Submission(sc) => sc.deleted,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct SubmissionComment {
|
|
id: Uuid,
|
|
submission_id: Uuid,
|
|
profile_id: Uuid,
|
|
body: String,
|
|
body_source: Option<String>,
|
|
published: DateTime<Utc>,
|
|
deleted: bool,
|
|
}
|
|
|
|
impl SubmissionComment {
|
|
fn update(&self) -> CommentChanges {
|
|
CommentChanges {
|
|
id: self.id,
|
|
body: None,
|
|
body_source: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ReplyComment {
|
|
id: Uuid,
|
|
submission_id: Uuid,
|
|
profile_id: Uuid,
|
|
comment_id: Uuid,
|
|
body: String,
|
|
body_source: Option<String>,
|
|
published: DateTime<Utc>,
|
|
deleted: bool,
|
|
}
|
|
|
|
impl ReplyComment {
|
|
fn update(&self) -> CommentChanges {
|
|
CommentChanges {
|
|
id: self.id,
|
|
body: None,
|
|
body_source: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum React {
|
|
Submission(SubmissionReact),
|
|
Reply(ReplyReact),
|
|
}
|
|
|
|
impl React {
|
|
pub(crate) fn id(&self) -> Uuid {
|
|
match self {
|
|
React::Submission(SubmissionReact { id, .. }) => *id,
|
|
React::Reply(ReplyReact { id, .. }) => *id,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn submission_id(&self) -> Uuid {
|
|
match self {
|
|
React::Submission(SubmissionReact { submission_id, .. }) => *submission_id,
|
|
React::Reply(ReplyReact { submission_id, .. }) => *submission_id,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn profile_id(&self) -> Uuid {
|
|
match self {
|
|
React::Submission(SubmissionReact { profile_id, .. }) => *profile_id,
|
|
React::Reply(ReplyReact { profile_id, .. }) => *profile_id,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn comment_id(&self) -> Option<Uuid> {
|
|
match self {
|
|
React::Reply(ReplyReact { comment_id, .. }) => Some(*comment_id),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn react(&self) -> &str {
|
|
match self {
|
|
React::Submission(SubmissionReact { react, .. }) => &react,
|
|
React::Reply(ReplyReact { react, .. }) => &react,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn published(&self) -> DateTime<Utc> {
|
|
match self {
|
|
React::Submission(SubmissionReact { published, .. }) => *published,
|
|
React::Reply(ReplyReact { published, .. }) => *published,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct SubmissionReact {
|
|
id: Uuid,
|
|
submission_id: Uuid,
|
|
profile_id: Uuid,
|
|
react: String,
|
|
published: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ReplyReact {
|
|
id: Uuid,
|
|
submission_id: Uuid,
|
|
profile_id: Uuid,
|
|
comment_id: Uuid,
|
|
react: String,
|
|
published: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Undo<T>(pub T);
|
|
|
|
#[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>,
|
|
}
|
|
|
|
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 any_changes(&self) -> bool {
|
|
self.display_name.is_some() || self.description.is_some() || self.login_required.is_some()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ProfileImageChanges {
|
|
id: Uuid,
|
|
icon: Option<Uuid>,
|
|
banner: Option<Uuid>,
|
|
}
|
|
|
|
impl ProfileImageChanges {
|
|
pub(crate) fn icon(&mut self, file: &File) -> &mut Self {
|
|
self.icon = Some(file.id);
|
|
self
|
|
}
|
|
|
|
pub(crate) fn banner(&mut self, file: &File) -> &mut Self {
|
|
self.banner = Some(file.id);
|
|
self
|
|
}
|
|
|
|
pub(crate) fn any_changes(&self) -> bool {
|
|
self.icon.is_some() || self.banner.is_some()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SubmissionChanges {
|
|
id: Uuid,
|
|
title: Option<String>,
|
|
title_source: Option<String>,
|
|
description: Option<String>,
|
|
description_source: Option<String>,
|
|
visibility: Option<Visibility>,
|
|
published: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
impl SubmissionChanges {
|
|
pub(crate) fn title(&mut self, title: &str) -> &mut Self {
|
|
self.title = Some(title.to_owned());
|
|
self
|
|
}
|
|
|
|
pub(crate) fn title_source(&mut self, title_source: &str) -> &mut Self {
|
|
self.title_source = Some(title_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 visibility(&mut self, visibility: Visibility) -> &mut Self {
|
|
self.visibility = Some(visibility);
|
|
self
|
|
}
|
|
|
|
pub(crate) fn publish(&mut self, time: Option<DateTime<Utc>>) -> &mut Self {
|
|
if self.published.is_none() {
|
|
self.published = time.or_else(|| Some(Utc::now()));
|
|
}
|
|
self
|
|
}
|
|
|
|
pub(crate) fn any_changes(&self) -> bool {
|
|
self.title.is_some()
|
|
|| self.description.is_some()
|
|
|| self.published.is_some()
|
|
|| self.visibility.is_some()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SubmissionFileChanges {
|
|
id: Uuid,
|
|
original_files: Vec<Uuid>,
|
|
files: Vec<Uuid>,
|
|
}
|
|
|
|
impl SubmissionFileChanges {
|
|
pub fn add_file(&mut self, file: &File) -> &mut Self {
|
|
self.files.push(file.id);
|
|
self
|
|
}
|
|
|
|
pub fn delete_file(&mut self, file: &File) -> &mut Self {
|
|
self.files.retain(|id| *id != file.id);
|
|
self
|
|
}
|
|
|
|
pub(crate) fn any_changes(&self) -> bool {
|
|
self.original_files != self.files
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct CommentChanges {
|
|
id: Uuid,
|
|
body: Option<String>,
|
|
body_source: Option<String>,
|
|
}
|
|
|
|
impl CommentChanges {
|
|
pub(crate) fn body(&mut self, body: &str) -> &mut CommentChanges {
|
|
self.body = Some(body.to_owned());
|
|
self
|
|
}
|
|
|
|
pub(crate) fn body_source(&mut self, body_source: &str) -> &mut CommentChanges {
|
|
self.body_source = Some(body_source.to_owned());
|
|
self
|
|
}
|
|
|
|
pub(crate) fn any_changes(&self) -> bool {
|
|
self.body.is_some()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum StoreError {
|
|
#[error("{0}")]
|
|
Json(#[from] serde_json::Error),
|
|
|
|
#[error("{0}")]
|
|
Sled(#[from] sled::Error),
|
|
|
|
#[error("{0}")]
|
|
Transaction(#[from] sled::transaction::TransactionError),
|
|
|
|
#[error("Profile changed during modification")]
|
|
DoubleStore,
|
|
|
|
#[error("Cannot update missing item")]
|
|
Missing,
|
|
|
|
#[error("Provided value is too long")]
|
|
TooLong,
|
|
|
|
#[error("Provided value must not be empty")]
|
|
Empty,
|
|
}
|
|
|
|
fn modify<T>(
|
|
tree: &sled::transaction::TransactionalTree,
|
|
key: &str,
|
|
f: impl Fn(&mut T),
|
|
) -> Result<(), sled::transaction::ConflictableTransactionError>
|
|
where
|
|
T: serde::Serialize + Default,
|
|
for<'de> T: serde::Deserialize<'de>,
|
|
{
|
|
let mut item = match tree.get(key.as_bytes())? {
|
|
Some(ivec) => {
|
|
let item: T = serde_json::from_slice(&ivec).expect("JSON is valid");
|
|
item
|
|
}
|
|
None => T::default(),
|
|
};
|
|
|
|
(f)(&mut item);
|
|
|
|
let item_vec = serde_json::to_vec(&item).expect("JSON is valid");
|
|
|
|
tree.insert(key.as_bytes(), item_vec.as_slice())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn count(
|
|
tree: &sled::transaction::TransactionalTree,
|
|
key: &str,
|
|
f: impl Fn(u64) -> u64,
|
|
) -> Result<(), sled::transaction::ConflictableTransactionError> {
|
|
let count = match tree.get(key.as_bytes())? {
|
|
Some(ivec) => {
|
|
let s = String::from_utf8_lossy(&ivec);
|
|
let count: u64 = s.parse().unwrap_or(0);
|
|
count
|
|
}
|
|
None => 0,
|
|
};
|
|
|
|
let count = (f)(count).to_string();
|
|
|
|
tree.insert(key.as_bytes(), count.as_bytes())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
impl fmt::Display for Visibility {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Visibility::Public => write!(f, "Public"),
|
|
Visibility::Unlisted => write!(f, "Unlisted"),
|
|
Visibility::Followers => write!(f, "Followers"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for OwnerSource {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
OwnerSource::Local(id) => write!(f, "local:{}", id),
|
|
OwnerSource::Remote(s) => write!(f, "remote:{}", s),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Store {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.debug_struct("Store")
|
|
.field("profiles", &"ProfileStore")
|
|
.field("files", &"FileStore")
|
|
.field("submissions", &"SubmissionStore")
|
|
.field("comments", &"CommentStore")
|
|
.field("reacts", &"ReactStore")
|
|
.field("view", &"ViewStore")
|
|
.finish()
|
|
}
|
|
}
|