Get a better handle on compound views

This commit is contained in:
Aode (Lion) 2022-05-14 22:24:21 -05:00
parent 14b428cc1c
commit ec0ae92bf5
8 changed files with 960 additions and 320 deletions

View file

@ -1,8 +1,39 @@
use bonsaidb::core::key::{EnumKey, Key, KeyEncoding};
use bonsaidb::core::key::{EnumKey, Key};
use num_derive::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Serialize};
use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset};
use uuid::Uuid;
mod compound_key;
mod datetime_key;
mod impossible;
mod tagged_key;
mod uuid_key;
pub use compound_key::CompoundKey;
pub use datetime_key::DatetimeKey;
pub use impossible::Impossible;
pub use tagged_key::TaggedKey;
pub use uuid_key::UuidKey;
pub struct Published;
pub struct Created;
pub struct Updated;
pub struct Deleted;
pub trait KeyExt<'k>: Sized {
fn tag<Tag>(self) -> TaggedKey<Self, Tag> {
TaggedKey::new(self)
}
fn compound<Secondary>(self, secondary: Secondary) -> CompoundKey<Self, Secondary> {
CompoundKey::new(self, secondary)
}
fn into_prefix(self) -> CompoundKey<Self, Impossible> {
CompoundKey::prefix(self)
}
}
impl<'k, T> KeyExt<'k> for T where T: Key<'k> {}
#[derive(
Clone,
@ -67,95 +98,18 @@ pub enum ReportState {
Removed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct DatetimeKey {
#[serde(with = "time::serde::rfc3339")]
datetime: OffsetDateTime,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct UuidKey {
uuid: Uuid,
}
impl DatetimeKey {
pub fn now() -> Self {
Self {
datetime: OffsetDateTime::now_utc(),
}
}
pub fn from_datetime(datetime: OffsetDateTime) -> Self {
Self {
datetime: datetime.to_offset(UtcOffset::UTC),
}
}
pub fn into_inner(self) -> OffsetDateTime {
self.datetime
}
}
impl UuidKey {
impl<Tag> TaggedKey<UuidKey, Tag> {
pub fn generate() -> Self {
Self {
uuid: Uuid::new_v4(),
}
UuidKey::generate().tag()
}
}
impl<Tag> TaggedKey<DatetimeKey, Tag> {
pub fn now() -> Self {
DatetimeKey::now().tag()
}
}
impl EnumKey for ProfileKind {}
impl EnumKey for PostKind {}
impl EnumKey for ReportState {}
impl<'k> KeyEncoding<'k, Self> for DatetimeKey {
type Error = time::Error;
const LENGTH: Option<usize> = None;
fn as_ord_bytes(&'k self) -> Result<std::borrow::Cow<'k, [u8]>, Self::Error> {
Ok(std::borrow::Cow::Owned(
self.datetime.format(&Rfc3339)?.into_bytes(),
))
}
}
impl<'k> Key<'k> for DatetimeKey {
fn from_ord_bytes(bytes: &'k [u8]) -> Result<Self, Self::Error> {
Ok(Self {
datetime: OffsetDateTime::parse(String::from_utf8_lossy(bytes).as_ref(), &Rfc3339)?,
})
}
}
impl<'k> KeyEncoding<'k, Self> for UuidKey {
type Error = uuid::Error;
const LENGTH: Option<usize> = Some(16);
fn as_ord_bytes(&'k self) -> Result<std::borrow::Cow<'k, [u8]>, Self::Error> {
Ok(std::borrow::Cow::Borrowed(self.uuid.as_bytes()))
}
}
impl<'k> Key<'k> for UuidKey {
fn from_ord_bytes(bytes: &'k [u8]) -> Result<Self, Self::Error> {
Ok(UuidKey {
uuid: Uuid::from_slice(bytes)?,
})
}
}
impl From<OffsetDateTime> for DatetimeKey {
fn from(datetime: OffsetDateTime) -> Self {
Self::from_datetime(datetime)
}
}
impl From<DatetimeKey> for OffsetDateTime {
fn from(key: DatetimeKey) -> Self {
key.into_inner()
}
}

View file

@ -0,0 +1,184 @@
use super::Impossible;
use bonsaidb::core::key::{Key, KeyEncoding};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct CompoundKey<Primary, Secondary> {
primary: Primary,
secondary: Option<Secondary>,
}
#[derive(Debug)]
pub enum CompoundKeyError<Primary, Secondary> {
Primary(Primary),
Secondary(Secondary),
InvalidFomat,
}
pub type CompoundError<'k, Primary, Secondary> = CompoundKeyError<
<Primary as KeyEncoding<'k, Primary>>::Error,
<Secondary as KeyEncoding<'k, Secondary>>::Error,
>;
impl<Primary> CompoundKey<Primary, Impossible> {
pub fn prefix(primary: Primary) -> Self {
Self {
primary,
secondary: None,
}
}
pub fn map_secondary<Secondary>(self) -> CompoundKey<Primary, Secondary> {
CompoundKey {
primary: self.primary,
secondary: None,
}
}
}
impl<Primary, Secondary> CompoundKey<Primary, Secondary> {
pub fn new(primary: Primary, secondary: Secondary) -> Self {
Self {
primary,
secondary: Some(secondary),
}
}
pub fn primary(&self) -> &Primary {
&self.primary
}
pub fn secondary(&self) -> Option<&Secondary> {
self.secondary.as_ref()
}
}
impl<Primary, Secondary> std::fmt::Display for CompoundKeyError<Primary, Secondary>
where
Primary: std::fmt::Display,
Secondary: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Primary(primary) => std::fmt::Display::fmt(primary, f),
Self::Secondary(secondary) => std::fmt::Display::fmt(secondary, f),
Self::InvalidFomat => write!(f, "Compound Key is invalid"),
}
}
}
impl<Primary, Secondary> std::error::Error for CompoundKeyError<Primary, Secondary>
where
Primary: std::error::Error + 'static,
Secondary: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Primary(primary) => Some(primary),
Self::Secondary(secondary) => Some(secondary),
Self::InvalidFomat => None,
}
}
}
impl<'k, Primary, Secondary> KeyEncoding<'k, Self> for CompoundKey<Primary, Secondary>
where
Primary: Key<'k>,
Secondary: Key<'k>,
{
type Error = CompoundError<'k, Primary, Secondary>;
const LENGTH: Option<usize> = None;
fn as_ord_bytes(&'k self) -> Result<std::borrow::Cow<'k, [u8]>, Self::Error> {
if let Some(secondary) = &self.secondary {
// key version
let primary = KeyEncoding::<'k, Primary>::as_ord_bytes(&self.primary)
.map_err(CompoundKeyError::Primary)?;
let secondary = KeyEncoding::<'k, Secondary>::as_ord_bytes(secondary)
.map_err(CompoundKeyError::Secondary)?;
let mut bytes = Vec::with_capacity(primary.len() + secondary.len() + 24);
bytes.extend_from_slice(&primary);
bytes.extend([0u8; 4]);
bytes.extend_from_slice(&secondary);
bytes.extend((primary.len() as u64).to_be_bytes());
bytes.extend((secondary.len() as u64).to_be_bytes());
bytes.extend([0, 0, 0, 1]);
Ok(std::borrow::Cow::Owned(bytes))
} else {
// prefix version
let primary = KeyEncoding::<'k, Primary>::as_ord_bytes(&self.primary)
.map_err(CompoundKeyError::Primary)?;
let mut bytes = Vec::with_capacity(primary.len() + 4);
bytes.extend_from_slice(&primary);
bytes.extend([0u8; 4]);
Ok(std::borrow::Cow::Owned(bytes))
}
}
}
impl<'k, Primary, Secondary> Key<'k> for CompoundKey<Primary, Secondary>
where
Primary: Key<'k>,
Secondary: Key<'k>,
{
fn from_ord_bytes(bytes: &'k [u8]) -> Result<Self, Self::Error> {
if bytes.len() < 4 {
return Err(CompoundKeyError::InvalidFomat);
}
let flags_start = bytes.len() - 4;
let flags = &bytes[flags_start..];
if flags == [0u8; 4] {
// prefix
let primary = Primary::from_ord_bytes(&bytes[..flags_start])
.map_err(CompoundKeyError::Primary)?;
return Ok(CompoundKey {
primary,
secondary: None,
});
}
if bytes.len() < 24 {
return Err(CompoundKeyError::InvalidFomat);
}
let secondary_len_start = flags_start - 8;
let primary_len_start = secondary_len_start - 8;
let secondary_len_bytes = TryFrom::try_from(&bytes[secondary_len_start..])
.expect("We've already length-checked this");
let primary_len_bytes = TryFrom::try_from(&bytes[primary_len_start..secondary_len_start])
.expect("We've already length-checked this");
let secondary_len = u64::from_be_bytes(secondary_len_bytes) as usize;
let primary_len = u64::from_be_bytes(primary_len_bytes) as usize;
let primary_start = 0;
let primary_end = primary_start + primary_len;
let secondary_start = primary_end + 4;
let secondary_end = secondary_start + secondary_len;
let primary = Primary::from_ord_bytes(&bytes[primary_start..primary_end])
.map_err(CompoundKeyError::Primary)?;
let secondary = Secondary::from_ord_bytes(&bytes[secondary_start..secondary_end])
.map_err(CompoundKeyError::Secondary)?;
Ok(CompoundKey {
primary,
secondary: Some(secondary),
})
}
}

View file

@ -0,0 +1,60 @@
use bonsaidb::core::key::{Key, KeyEncoding};
use serde::{Deserialize, Serialize};
use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct DatetimeKey {
#[serde(with = "time::serde::rfc3339")]
datetime: OffsetDateTime,
}
impl DatetimeKey {
pub fn now() -> Self {
Self {
datetime: OffsetDateTime::now_utc(),
}
}
pub fn from_datetime(datetime: OffsetDateTime) -> Self {
Self {
datetime: datetime.to_offset(UtcOffset::UTC),
}
}
pub fn into_inner(self) -> OffsetDateTime {
self.datetime
}
}
impl<'k> KeyEncoding<'k, Self> for DatetimeKey {
type Error = time::Error;
const LENGTH: Option<usize> = None;
fn as_ord_bytes(&'k self) -> Result<std::borrow::Cow<'k, [u8]>, Self::Error> {
Ok(std::borrow::Cow::Owned(
self.datetime.format(&Rfc3339)?.into_bytes(),
))
}
}
impl<'k> Key<'k> for DatetimeKey {
fn from_ord_bytes(bytes: &'k [u8]) -> Result<Self, Self::Error> {
Ok(Self {
datetime: OffsetDateTime::parse(String::from_utf8_lossy(bytes).as_ref(), &Rfc3339)?,
})
}
}
impl From<OffsetDateTime> for DatetimeKey {
fn from(datetime: OffsetDateTime) -> Self {
Self::from_datetime(datetime)
}
}
impl From<DatetimeKey> for OffsetDateTime {
fn from(key: DatetimeKey) -> Self {
key.into_inner()
}
}

View file

@ -0,0 +1,32 @@
use bonsaidb::core::key::{Key, KeyEncoding};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Impossible {}
impl std::fmt::Display for Impossible {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {}
}
}
impl std::error::Error for Impossible {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {}
}
}
impl<'k> KeyEncoding<'k, Self> for Impossible {
type Error = Impossible;
const LENGTH: Option<usize> = None;
fn as_ord_bytes(&'k self) -> Result<std::borrow::Cow<'k, [u8]>, Self::Error> {
match *self {}
}
}
impl<'k> Key<'k> for Impossible {
fn from_ord_bytes(_bytes: &'k [u8]) -> Result<Self, Self::Error> {
panic!("Should never construct an impossible type")
}
}

View file

@ -0,0 +1,167 @@
use bonsaidb::core::key::{Key, KeyEncoding};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
pub struct TaggedKey<Inner, Tag> {
inner: Inner,
tag: PhantomData<fn() -> Tag>,
}
impl<Inner, Tag> TaggedKey<Inner, Tag> {
pub fn new(inner: Inner) -> Self {
Self {
inner,
tag: PhantomData,
}
}
}
impl<Inner, Tag> Clone for TaggedKey<Inner, Tag>
where
Inner: Clone,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
tag: PhantomData,
}
}
}
impl<Inner, Tag> Copy for TaggedKey<Inner, Tag> where Inner: Copy {}
impl<Inner, Tag> std::fmt::Debug for TaggedKey<Inner, Tag>
where
Inner: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.inner, f)
}
}
impl<Inner, Tag> std::fmt::Display for TaggedKey<Inner, Tag>
where
Inner: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.inner, f)
}
}
impl<Inner, Tag> PartialEq for TaggedKey<Inner, Tag>
where
Inner: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<Inner, Tag> Eq for TaggedKey<Inner, Tag> where Inner: Eq {}
impl<Inner, Tag> PartialOrd for TaggedKey<Inner, Tag>
where
Inner: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.inner.partial_cmp(&other.inner)
}
}
impl<Inner, Tag> Ord for TaggedKey<Inner, Tag>
where
Inner: Ord,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.inner.cmp(&other.inner)
}
}
impl<Inner, Tag> std::hash::Hash for TaggedKey<Inner, Tag>
where
Inner: std::hash::Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.hash(state)
}
}
impl<'de, Inner, Tag> Deserialize<'de> for TaggedKey<Inner, Tag>
where
Inner: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let inner = Inner::deserialize(deserializer)?;
Ok(Self {
inner,
tag: PhantomData,
})
}
}
impl<Inner, Tag> Serialize for TaggedKey<Inner, Tag>
where
Inner: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
self.inner.serialize(serializer)
}
}
impl<'k, Inner, Tag> KeyEncoding<'k, Self> for TaggedKey<Inner, Tag>
where
Inner: Key<'k>,
{
type Error = Inner::Error;
const LENGTH: Option<usize> = Inner::LENGTH;
fn as_ord_bytes(&'k self) -> Result<std::borrow::Cow<'k, [u8]>, Self::Error> {
self.inner.as_ord_bytes()
}
}
impl<'k, Inner, Tag> Key<'k> for TaggedKey<Inner, Tag>
where
Inner: Key<'k>,
{
fn from_ord_bytes(bytes: &'k [u8]) -> Result<Self, Self::Error> {
Ok(Self {
inner: Inner::from_ord_bytes(bytes)?,
tag: PhantomData,
})
}
fn first_value() -> Result<Self, bonsaidb::core::key::NextValueError> {
Ok(Self {
inner: Inner::first_value()?,
tag: PhantomData,
})
}
fn next_value(&self) -> Result<Self, bonsaidb::core::key::NextValueError> {
Ok(Self {
inner: self.inner.next_value()?,
tag: PhantomData,
})
}
}
impl<Inner, Tag> AsRef<Inner> for TaggedKey<Inner, Tag> {
fn as_ref(&self) -> &Inner {
&self.inner
}
}
impl<Inner, Tag> AsMut<Inner> for TaggedKey<Inner, Tag> {
fn as_mut(&mut self) -> &mut Inner {
&mut self.inner
}
}

View file

@ -0,0 +1,35 @@
use bonsaidb::core::key::{Key, KeyEncoding};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct UuidKey {
uuid: Uuid,
}
impl UuidKey {
pub fn generate() -> Self {
Self {
uuid: Uuid::new_v4(),
}
}
}
impl<'k> KeyEncoding<'k, Self> for UuidKey {
type Error = uuid::Error;
const LENGTH: Option<usize> = Some(16);
fn as_ord_bytes(&'k self) -> Result<std::borrow::Cow<'k, [u8]>, Self::Error> {
Ok(std::borrow::Cow::Borrowed(self.uuid.as_bytes()))
}
}
impl<'k> Key<'k> for UuidKey {
fn from_ord_bytes(bytes: &'k [u8]) -> Result<Self, Self::Error> {
Ok(UuidKey {
uuid: Uuid::from_slice(bytes)?,
})
}
}

View file

@ -1,4 +1,5 @@
pub mod keys;
pub mod post;
use bonsaidb::core::{
document::{CollectionDocument, Emit},
@ -7,7 +8,8 @@ use bonsaidb::core::{
Collection, ReduceResult, Schema, View, ViewMapResult,
},
};
use keys::{DatetimeKey, PostKind, ProfileKind, ReportState, UuidKey};
use keys::{Created, DatetimeKey, KeyExt, ProfileKind, ReportState, TaggedKey, Updated, UuidKey};
use post::Post;
use serde::{Deserialize, Serialize};
#[derive(Debug, Schema)]
@ -28,24 +30,28 @@ pub struct SiteSchema;
#[collection(
name = "accounts",
views = [AccountsByUsername, AccountsByEmail],
primary_key = UuidKey,
primary_key = TaggedKey<UuidKey, Account>,
natural_id = |account: &Account| Some(account.id)
)]
pub struct Account {
id: UuidKey,
id: TaggedKey<UuidKey, Self>,
username: String,
emails: Vec<String>,
created_at: TaggedKey<DatetimeKey, Created>,
updated_at: TaggedKey<DatetimeKey, Updated>,
}
#[derive(Collection, Debug, Deserialize, Serialize)]
#[collection(
name = "profiles",
views = [ProfilesByHandle, RemoteProfilesByDomain, LocalProfilesById, ProfilesByCollaborator, ProfilesByKind],
primary_key = UuidKey,
primary_key = TaggedKey<UuidKey, Profile>,
natural_id = |profile: &Profile| Some(profile.id)
)]
pub struct Profile {
id: UuidKey,
id: TaggedKey<UuidKey, Self>,
kind: ProfileKind,
@ -56,13 +62,11 @@ pub struct Profile {
name: Option<String>,
description: Option<String>,
// Account Key
local_account: Option<UuidKey>,
// Profile Keys
collaborators: Vec<UuidKey>,
local_account: Option<TaggedKey<UuidKey, Account>>,
collaborators: Vec<TaggedKey<UuidKey, Profile>>,
created_at: DatetimeKey,
updated_at: DatetimeKey,
created_at: TaggedKey<DatetimeKey, Created>,
updated_at: TaggedKey<DatetimeKey, Updated>,
}
#[derive(Debug, Deserialize, Serialize)]
@ -81,108 +85,55 @@ pub enum MediaState {
#[collection(
name = "media",
views = [],
primary_key = UuidKey,
primary_key = TaggedKey<UuidKey, Media>,
natural_id = |media: &Media| Some(media.id)
)]
pub struct Media {
id: UuidKey,
id: TaggedKey<UuidKey, Self>,
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,
created_at: TaggedKey<DatetimeKey, Created>,
updated_at: TaggedKey<DatetimeKey, Updated>,
}
#[derive(Collection, Debug, Deserialize, Serialize)]
#[collection(
name = "tags",
views = [TagsByName],
primary_key = UuidKey,
primary_key = TaggedKey<UuidKey, Tag>,
natural_id = |tag: &Tag| Some(tag.id)
)]
pub struct Tag {
id: UuidKey,
id: TaggedKey<UuidKey, Self>,
name: String,
created_at: TaggedKey<DatetimeKey, Created>,
updated_at: TaggedKey<DatetimeKey, Updated>,
}
#[derive(Collection, Debug, Deserialize, Serialize)]
#[collection(
name = "reports",
views = [ReportsByPost, ReportsByState, ReportsByAccount],
primary_key = UuidKey,
primary_key = TaggedKey<UuidKey, Report>,
natural_id = |report: &Report| Some(report.id)
)]
pub struct Report {
id: UuidKey,
id: TaggedKey<UuidKey, Self>,
message: Option<String>,
moderation_note: Option<String>,
// Post key
post: UuidKey,
post: TaggedKey<UuidKey, Post>,
state: ReportState,
// Account Key
handled_by: Option<UuidKey>,
handled_by: Option<TaggedKey<UuidKey, Account>>,
created_at: DatetimeKey,
updated_at: DatetimeKey,
created_at: TaggedKey<DatetimeKey, Created>,
updated_at: TaggedKey<DatetimeKey, Updated>,
}
#[derive(Debug, Clone, View)]
@ -202,43 +153,23 @@ pub struct ProfilesByHandle;
pub struct RemoteProfilesByDomain;
#[derive(Debug, Clone, View)]
#[view(collection = Profile, key = Option<UuidKey>, value = usize, name = "by-local-id")]
#[view(collection = Profile, key = Option<TaggedKey<UuidKey, Account>>, value = usize, name = "by-local-id")]
pub struct LocalProfilesById;
#[derive(Debug, Clone, View)]
#[view(collection = Profile, key = UuidKey, value = usize, name = "by-collaborator")]
#[view(collection = Profile, key = TaggedKey<UuidKey, Profile>, 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")]
#[view(collection = Report, key = TaggedKey<UuidKey, Post>, value = usize, name = "by-name")]
pub struct ReportsByPost;
#[derive(Debug, Clone, View)]
@ -246,7 +177,7 @@ pub struct ReportsByPost;
pub struct ReportsByState;
#[derive(Debug, Clone, View)]
#[view(collection = Report, key = Option<UuidKey>, value = usize, name = "by-account")]
#[view(collection = Report, key = Option<TaggedKey<UuidKey, Account>>, value = usize, name = "by-account")]
pub struct ReportsByAccount;
impl CollectionViewSchema for AccountsByUsername {
@ -278,7 +209,7 @@ impl CollectionViewSchema for AccountsByEmail {
document: CollectionDocument<<Self::View as View>::Collection>,
) -> ViewMapResult<Self::View> {
if document.contents.emails.is_empty() {
return Ok(Mappings::Simple(None));
return Ok(Mappings::none());
}
let mut mappings = Vec::new();
@ -309,7 +240,7 @@ impl CollectionViewSchema for ProfilesByHandle {
let mut mappings = Vec::new();
if document.contents.handle.is_empty() {
return Ok(Mappings::Simple(None));
return Ok(Mappings::none());
}
const MAX_SEARCH_LEN: usize = 16;
@ -396,7 +327,7 @@ impl CollectionViewSchema for ProfilesByCollaborator {
document: CollectionDocument<<Self::View as View>::Collection>,
) -> ViewMapResult<Self::View> {
if document.contents.collaborators.is_empty() {
return Ok(Mappings::Simple(None));
return Ok(Mappings::none());
}
let mut mappings = Vec::new();
@ -437,131 +368,6 @@ impl CollectionViewSchema for ProfilesByKind {
}
}
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;
@ -649,8 +455,10 @@ impl CollectionViewSchema for ReportsByAccount {
impl Tag {
pub fn new(name: String) -> Self {
Self {
id: UuidKey::generate(),
id: UuidKey::generate().tag(),
name,
created_at: DatetimeKey::now().tag(),
updated_at: DatetimeKey::now().tag(),
}
}

400
schema/src/post.rs Normal file
View file

@ -0,0 +1,400 @@
use super::{
keys::{
CompoundKey, Created, DatetimeKey, Deleted, KeyExt, PostKind, Published, TaggedKey,
Updated, UuidKey,
},
Account, Media, Profile, Tag,
};
use bonsaidb::core::{
document::{CollectionDocument, Emit},
schema::{
view::{map::Mappings, CollectionViewSchema},
Collection, ReduceResult, View, ViewMapResult,
},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum DeletedBy {
Author,
PageOwner {
profile_id: TaggedKey<UuidKey, Profile>,
},
Moderator {
account_id: TaggedKey<UuidKey, Account>,
},
}
#[derive(Collection, Debug, Deserialize, Serialize)]
#[collection(
name = "post",
views = [PostsByKind, PublishedPostsByKind, PostsByAuthor, PublishedPostsByAuthor, UnpublishedPostsByAuthor, UnpublishedPostsByAuthorAndKind, PostsByTag, PostsByMention, PostsByParent],
primary_key = TaggedKey<UuidKey, Post>,
natural_id = |post: &Post| Some(post.id)
)]
pub struct Post {
id: TaggedKey<UuidKey, Self>,
deleted_by: Option<DeletedBy>,
title: String,
description: Option<String>,
media: Vec<TaggedKey<UuidKey, Media>>,
tags: Vec<TaggedKey<UuidKey, Tag>>,
mentions: Vec<TaggedKey<UuidKey, Profile>>,
reply_to: Option<TaggedKey<UuidKey, Self>>,
author: TaggedKey<UuidKey, Profile>,
created_at: TaggedKey<DatetimeKey, Created>,
updated_at: TaggedKey<DatetimeKey, Updated>,
published_at: Option<TaggedKey<DatetimeKey, Published>>,
deleted_at: Option<TaggedKey<DatetimeKey, Deleted>>,
}
pub type PostKindPublishedKey = CompoundKey<PostKind, TaggedKey<DatetimeKey, Published>>;
pub type AuthorPublishedKey =
CompoundKey<TaggedKey<UuidKey, Profile>, TaggedKey<DatetimeKey, Published>>;
pub type AuthorPostKindPublishedKey = CompoundKey<
CompoundKey<TaggedKey<UuidKey, Profile>, PostKind>,
TaggedKey<DatetimeKey, Published>,
>;
pub type AuthorUnpublishedKey =
CompoundKey<TaggedKey<UuidKey, Profile>, TaggedKey<DatetimeKey, Created>>;
pub type AuthorPostKindUnpublishedKey = CompoundKey<
CompoundKey<TaggedKey<UuidKey, Profile>, PostKind>,
TaggedKey<DatetimeKey, Created>,
>;
#[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 = PostKindPublishedKey, value = usize, name = "by-kind-and-published")]
pub struct PublishedPostsByKind;
#[derive(Debug, Clone, View)]
#[view(collection = Post, key = TaggedKey<UuidKey, Profile>, value = usize, name = "by-author")]
pub struct PostsByAuthor;
#[derive(Debug, Clone, View)]
#[view(collection = Post, key = AuthorPublishedKey, value = usize, name = "by-author-and-published")]
pub struct PublishedPostsByAuthor;
#[derive(Debug, Clone, View)]
#[view(collection = Post, key = AuthorPostKindPublishedKey, value = usize, name = "by-author-and-published")]
pub struct PublishedPostsByAuthorAndKind;
#[derive(Debug, Clone, View)]
#[view(collection = Post, key = AuthorUnpublishedKey, value = usize, name = "by-author-and-unpublished")]
pub struct UnpublishedPostsByAuthor;
#[derive(Debug, Clone, View)]
#[view(collection = Post, key = AuthorPostKindUnpublishedKey, value = usize, name = "by-author-and-unpublished")]
pub struct UnpublishedPostsByAuthorAndKind;
#[derive(Debug, Clone, View)]
#[view(collection = Post, key = TaggedKey<UuidKey, Tag>, value = usize, name = "by-tag")]
pub struct PostsByTag;
#[derive(Debug, Clone, View)]
#[view(collection = Post, key = TaggedKey<UuidKey, Profile>, value = usize, name = "by-mention")]
pub struct PostsByMention;
#[derive(Debug, Clone, View)]
#[view(collection = Post, key = Option<TaggedKey<UuidKey, Post>>, value = usize, name = "by-parent")]
pub struct PostsByParent;
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 PublishedPostsByKind {
type View = Self;
fn map(
&self,
document: CollectionDocument<<Self as View>::Collection>,
) -> ViewMapResult<Self::View> {
if let Some(published_at) = document.contents.published_at {
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.compound(published_at), 1)
} else {
Ok(Mappings::none())
}
}
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 PublishedPostsByAuthor {
type View = Self;
fn map(
&self,
document: CollectionDocument<<Self::View as View>::Collection>,
) -> ViewMapResult<Self::View> {
if let Some(published_at) = document.contents.published_at {
document
.header
.emit_key_and_value(document.contents.author.compound(published_at), 1)
} else {
Ok(Mappings::none())
}
}
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 PublishedPostsByAuthorAndKind {
type View = Self;
fn map(
&self,
document: CollectionDocument<<Self::View as View>::Collection>,
) -> ViewMapResult<Self::View> {
if let Some(published_at) = document.contents.published_at {
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(
document
.contents
.author
.compound(kind)
.compound(published_at),
1,
)
} else {
Ok(Mappings::none())
}
}
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 UnpublishedPostsByAuthor {
type View = Self;
fn map(
&self,
document: CollectionDocument<<Self::View as View>::Collection>,
) -> ViewMapResult<Self::View> {
if document.contents.published_at.is_some() {
Ok(Mappings::none())
} else {
document.header.emit_key_and_value(
document
.contents
.author
.compound(document.contents.created_at),
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 UnpublishedPostsByAuthorAndKind {
type View = Self;
fn map(
&self,
document: CollectionDocument<<Self::View as View>::Collection>,
) -> ViewMapResult<Self::View> {
if document.contents.published_at.is_some() {
Ok(Mappings::none())
} else {
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(
document
.contents
.author
.compound(kind)
.compound(document.contents.created_at),
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::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::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())
}
}