661 lines
16 KiB
Rust
661 lines
16 KiB
Rust
pub mod keys;
|
|
|
|
use bonsaidb::core::{
|
|
document::{CollectionDocument, Emit},
|
|
schema::{
|
|
view::{map::Mappings, CollectionViewSchema},
|
|
Collection, ReduceResult, Schema, View, ViewMapResult,
|
|
},
|
|
};
|
|
use keys::{DatetimeKey, PostKind, ProfileKind, ReportState, UuidKey};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Schema)]
|
|
#[schema(
|
|
name = "SiteSchema",
|
|
collections = [
|
|
Account,
|
|
Profile,
|
|
Media,
|
|
Post,
|
|
Tag,
|
|
Report,
|
|
]
|
|
)]
|
|
pub struct SiteSchema;
|
|
|
|
#[derive(Collection, Debug, Deserialize, Serialize)]
|
|
#[collection(
|
|
name = "accounts",
|
|
views = [AccountsByUsername, AccountsByEmail],
|
|
primary_key = UuidKey,
|
|
natural_id = |account: &Account| Some(account.id)
|
|
)]
|
|
pub struct Account {
|
|
id: UuidKey,
|
|
username: String,
|
|
emails: Vec<String>,
|
|
}
|
|
|
|
#[derive(Collection, Debug, Deserialize, Serialize)]
|
|
#[collection(
|
|
name = "profiles",
|
|
views = [ProfilesByHandle, RemoteProfilesByDomain, LocalProfilesById, ProfilesByCollaborator, ProfilesByKind],
|
|
primary_key = UuidKey,
|
|
natural_id = |profile: &Profile| Some(profile.id)
|
|
)]
|
|
pub struct Profile {
|
|
id: UuidKey,
|
|
|
|
kind: ProfileKind,
|
|
|
|
handle: String,
|
|
domain: Option<String>,
|
|
full_url: Option<String>,
|
|
|
|
name: Option<String>,
|
|
description: Option<String>,
|
|
|
|
// Account Key
|
|
local_account: Option<UuidKey>,
|
|
// Profile Keys
|
|
collaborators: Vec<UuidKey>,
|
|
|
|
created_at: DatetimeKey,
|
|
updated_at: DatetimeKey,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
#[serde(tag = "type")]
|
|
pub enum MediaState {
|
|
Uploading {
|
|
upload_id: String,
|
|
},
|
|
Finished {
|
|
filename: String,
|
|
delete_token: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Collection, Debug, Deserialize, Serialize)]
|
|
#[collection(
|
|
name = "media",
|
|
views = [],
|
|
primary_key = UuidKey,
|
|
natural_id = |media: &Media| Some(media.id)
|
|
)]
|
|
pub struct Media {
|
|
id: UuidKey,
|
|
|
|
state: MediaState,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
#[serde(tag = "type")]
|
|
pub enum DeletedBy {
|
|
Author,
|
|
PageOwner {
|
|
// Profile key
|
|
profile_id: UuidKey,
|
|
},
|
|
Moderator {
|
|
// Account key
|
|
account_id: UuidKey,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct History {
|
|
content: String,
|
|
date: DatetimeKey,
|
|
}
|
|
|
|
#[derive(Collection, Debug, Deserialize, Serialize)]
|
|
#[collection(
|
|
name = "post",
|
|
views = [PostsByKind, PostsByAuthor, PostsByTag, PostsByMention, PostsByParent],
|
|
primary_key = UuidKey,
|
|
natural_id = |post: &Post| Some(post.id)
|
|
)]
|
|
pub struct Post {
|
|
id: UuidKey,
|
|
|
|
deleted_by: Option<DeletedBy>,
|
|
|
|
title: String,
|
|
title_history: Vec<History>,
|
|
|
|
description: Option<String>,
|
|
description_history: Vec<History>,
|
|
|
|
// Media key
|
|
media: Vec<UuidKey>,
|
|
|
|
// Tag key
|
|
tags: Vec<UuidKey>,
|
|
|
|
// Profile key
|
|
mentions: Vec<UuidKey>,
|
|
|
|
// Post key
|
|
reply_to: Option<UuidKey>,
|
|
|
|
// Profile key
|
|
author: UuidKey,
|
|
|
|
created_at: DatetimeKey,
|
|
updated_at: DatetimeKey,
|
|
}
|
|
|
|
#[derive(Collection, Debug, Deserialize, Serialize)]
|
|
#[collection(
|
|
name = "tags",
|
|
views = [TagsByName],
|
|
primary_key = UuidKey,
|
|
natural_id = |tag: &Tag| Some(tag.id)
|
|
)]
|
|
pub struct Tag {
|
|
id: UuidKey,
|
|
|
|
name: String,
|
|
}
|
|
|
|
#[derive(Collection, Debug, Deserialize, Serialize)]
|
|
#[collection(
|
|
name = "reports",
|
|
views = [ReportsByPost, ReportsByState, ReportsByAccount],
|
|
primary_key = UuidKey,
|
|
natural_id = |report: &Report| Some(report.id)
|
|
)]
|
|
pub struct Report {
|
|
id: UuidKey,
|
|
|
|
message: Option<String>,
|
|
moderation_note: Option<String>,
|
|
|
|
// Post key
|
|
post: UuidKey,
|
|
|
|
state: ReportState,
|
|
|
|
// Account Key
|
|
handled_by: Option<UuidKey>,
|
|
|
|
created_at: DatetimeKey,
|
|
updated_at: DatetimeKey,
|
|
}
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Account, key = String, value = usize, name = "by-username")]
|
|
pub struct AccountsByUsername;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Account, key = String, value = usize, name = "by-email")]
|
|
pub struct AccountsByEmail;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Profile, key = String, value = usize, name = "by-handle")]
|
|
pub struct ProfilesByHandle;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Profile, key = Option<String>, value = usize, name = "by-remote-domain")]
|
|
pub struct RemoteProfilesByDomain;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Profile, key = Option<UuidKey>, value = usize, name = "by-local-id")]
|
|
pub struct LocalProfilesById;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Profile, key = UuidKey, value = usize, name = "by-collaborator")]
|
|
pub struct ProfilesByCollaborator;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Profile, key = ProfileKind, value = usize, name = "by-kind")]
|
|
pub struct ProfilesByKind;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Post, key = PostKind, value = usize, name = "by-kind")]
|
|
pub struct PostsByKind;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Post, key = UuidKey, value = usize, name = "by-author")]
|
|
pub struct PostsByAuthor;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Post, key = UuidKey, value = usize, name = "by-tag")]
|
|
pub struct PostsByTag;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Post, key = UuidKey, value = usize, name = "by-mention")]
|
|
pub struct PostsByMention;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Post, key = Option<UuidKey>, value = usize, name = "by-parent")]
|
|
pub struct PostsByParent;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Tag, key = String, value = usize, name = "by-name")]
|
|
pub struct TagsByName;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Report, key = UuidKey, value = usize, name = "by-name")]
|
|
pub struct ReportsByPost;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Report, key = ReportState, value = usize, name = "by-state")]
|
|
pub struct ReportsByState;
|
|
|
|
#[derive(Debug, Clone, View)]
|
|
#[view(collection = Report, key = Option<UuidKey>, value = usize, name = "by-account")]
|
|
pub struct ReportsByAccount;
|
|
|
|
impl CollectionViewSchema for AccountsByUsername {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.username, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for AccountsByEmail {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
if document.contents.emails.is_empty() {
|
|
return Ok(Mappings::Simple(None));
|
|
}
|
|
|
|
let mut mappings = Vec::new();
|
|
|
|
for email in document.contents.emails {
|
|
mappings.extend(document.header.emit_key_and_value(email, 1)?);
|
|
}
|
|
|
|
Ok(Mappings::List(mappings))
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for ProfilesByHandle {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
let mut mappings = Vec::new();
|
|
|
|
if document.contents.handle.is_empty() {
|
|
return Ok(Mappings::Simple(None));
|
|
}
|
|
|
|
const MAX_SEARCH_LEN: usize = 16;
|
|
|
|
// enable searching for profiles by handle lol
|
|
// turns 'asonix' into 'a', 'as', 'aso', 'ason', 'asoni', 'asonix'
|
|
// cap search characters
|
|
for i in 1..=document.contents.handle.len().min(MAX_SEARCH_LEN) {
|
|
mappings.extend(
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.handle[..i].to_owned(), 1)?,
|
|
);
|
|
}
|
|
|
|
// Ensure we index the full handle if longer than max characters as well
|
|
if document.contents.handle.len() > MAX_SEARCH_LEN {
|
|
mappings.extend(
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.handle, 1)?,
|
|
);
|
|
}
|
|
|
|
Ok(Mappings::List(mappings))
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for RemoteProfilesByDomain {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.domain, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for LocalProfilesById {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.local_account, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for ProfilesByCollaborator {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
if document.contents.collaborators.is_empty() {
|
|
return Ok(Mappings::Simple(None));
|
|
}
|
|
|
|
let mut mappings = Vec::new();
|
|
for collaborator in document.contents.collaborators {
|
|
mappings.extend(document.header.emit_key_and_value(collaborator, 1)?);
|
|
}
|
|
|
|
Ok(Mappings::List(mappings))
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for ProfilesByKind {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.kind, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for PostsByKind {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
let kind = if !document.contents.media.is_empty() {
|
|
PostKind::Media
|
|
} else if document.contents.reply_to.is_some() {
|
|
PostKind::Comment
|
|
} else {
|
|
PostKind::Text
|
|
};
|
|
|
|
document.header.emit_key_and_value(kind, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for PostsByAuthor {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.author, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for PostsByTag {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
if document.contents.tags.is_empty() {
|
|
return Ok(Mappings::Simple(None));
|
|
}
|
|
|
|
let mut mappings = Vec::new();
|
|
for tag in document.contents.tags {
|
|
mappings.extend(document.header.emit_key_and_value(tag, 1)?);
|
|
}
|
|
|
|
Ok(Mappings::List(mappings))
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for PostsByMention {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
if document.contents.mentions.is_empty() {
|
|
return Ok(Mappings::Simple(None));
|
|
}
|
|
|
|
let mut mappings = Vec::new();
|
|
for mention in document.contents.mentions {
|
|
mappings.extend(document.header.emit_key_and_value(mention, 1)?);
|
|
}
|
|
|
|
Ok(Mappings::List(mappings))
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for PostsByParent {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.reply_to, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for TagsByName {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.name, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for ReportsByPost {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.post, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for ReportsByState {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.state, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl CollectionViewSchema for ReportsByAccount {
|
|
type View = Self;
|
|
|
|
fn map(
|
|
&self,
|
|
document: CollectionDocument<<Self::View as View>::Collection>,
|
|
) -> ViewMapResult<Self::View> {
|
|
document
|
|
.header
|
|
.emit_key_and_value(document.contents.handled_by, 1)
|
|
}
|
|
|
|
fn reduce(
|
|
&self,
|
|
mappings: &[bonsaidb::core::schema::ViewMappedValue<Self::View>],
|
|
_rereduce: bool,
|
|
) -> ReduceResult<Self::View> {
|
|
Ok(mappings.iter().map(|m| m.value).sum())
|
|
}
|
|
}
|
|
|
|
impl Tag {
|
|
pub fn new(name: String) -> Self {
|
|
Self {
|
|
id: UuidKey::generate(),
|
|
name,
|
|
}
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
}
|