fix: generic JsonVec that accepts null
not the cleanest solution but should be generic and transparent
This commit is contained in:
parent
88b87c0b20
commit
fafe5307c5
9 changed files with 119 additions and 61 deletions
|
@ -1,4 +1,4 @@
|
||||||
use upub::model::{addressing, config, credential, activity, object, actor, Audience};
|
use upub::{ext::JsonVec, model::{activity, actor, addressing, config, credential, object}};
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use sea_orm::{ActiveValue::NotSet, IntoActiveModel};
|
use sea_orm::{ActiveValue::NotSet, IntoActiveModel};
|
||||||
|
|
||||||
|
@ -21,8 +21,8 @@ pub async fn faker(ctx: upub::Context, count: i64) -> Result<(), sea_orm::DbErr>
|
||||||
followers: None,
|
followers: None,
|
||||||
followers_count: 0,
|
followers_count: 0,
|
||||||
statuses_count: count as i32,
|
statuses_count: count as i32,
|
||||||
fields: vec![],
|
fields: JsonVec::default(),
|
||||||
also_known_as: vec![],
|
also_known_as: JsonVec::default(),
|
||||||
moved_to: None,
|
moved_to: None,
|
||||||
icon: Some("https://cdn.alemi.dev/social/circle-square.png".to_string()),
|
icon: Some("https://cdn.alemi.dev/social/circle-square.png".to_string()),
|
||||||
image: Some("https://cdn.alemi.dev/social/someriver-xs.jpg".to_string()),
|
image: Some("https://cdn.alemi.dev/social/someriver-xs.jpg".to_string()),
|
||||||
|
@ -90,10 +90,10 @@ pub async fn faker(ctx: upub::Context, count: i64) -> Result<(), sea_orm::DbErr>
|
||||||
likes: Set(0),
|
likes: Set(0),
|
||||||
announces: Set(0),
|
announces: Set(0),
|
||||||
audience: Set(None),
|
audience: Set(None),
|
||||||
to: Set(Audience(vec![apb::target::PUBLIC.to_string()])),
|
to: Set(JsonVec(vec![apb::target::PUBLIC.to_string()])),
|
||||||
bto: Set(Audience::default()),
|
bto: Set(JsonVec::default()),
|
||||||
cc: Set(Audience(vec![])),
|
cc: Set(JsonVec(vec![])),
|
||||||
bcc: Set(Audience::default()),
|
bcc: Set(JsonVec::default()),
|
||||||
url: Set(None),
|
url: Set(None),
|
||||||
sensitive: Set(false),
|
sensitive: Set(false),
|
||||||
}).exec(db).await?;
|
}).exec(db).await?;
|
||||||
|
@ -106,10 +106,10 @@ pub async fn faker(ctx: upub::Context, count: i64) -> Result<(), sea_orm::DbErr>
|
||||||
object: Set(Some(format!("{domain}/objects/{oid}"))),
|
object: Set(Some(format!("{domain}/objects/{oid}"))),
|
||||||
target: Set(None),
|
target: Set(None),
|
||||||
published: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i as u64)),
|
published: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i as u64)),
|
||||||
to: Set(Audience(vec![apb::target::PUBLIC.to_string()])),
|
to: Set(JsonVec(vec![apb::target::PUBLIC.to_string()])),
|
||||||
bto: Set(Audience::default()),
|
bto: Set(JsonVec::default()),
|
||||||
cc: Set(Audience(vec![])),
|
cc: Set(JsonVec(vec![])),
|
||||||
bcc: Set(Audience::default()),
|
bcc: Set(JsonVec::default()),
|
||||||
}).exec(db).await?;
|
}).exec(db).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,3 +48,66 @@ impl<T, E: std::error::Error> LoggableError for Result<T, E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct JsonVec<T>(pub Vec<T>);
|
||||||
|
|
||||||
|
impl<T> From<Vec<T>> for JsonVec<T> {
|
||||||
|
fn from(value: Vec<T>) -> Self {
|
||||||
|
JsonVec(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for JsonVec<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
JsonVec(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: serde::de::DeserializeOwned> sea_orm::TryGetableFromJson for JsonVec<T> {}
|
||||||
|
|
||||||
|
impl<T: serde::ser::Serialize> std::convert::From<JsonVec<T>> for sea_orm::Value {
|
||||||
|
fn from(source: JsonVec<T>) -> Self {
|
||||||
|
sea_orm::Value::Json(serde_json::to_value(&source).ok().map(std::boxed::Box::new))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: serde::de::DeserializeOwned + TypeName> sea_orm::sea_query::ValueType for JsonVec<T> {
|
||||||
|
fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
|
||||||
|
match v {
|
||||||
|
sea_orm::Value::Json(Some(json)) => Ok(
|
||||||
|
serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?,
|
||||||
|
),
|
||||||
|
sea_orm::Value::Json(None) => Ok(JsonVec::default()),
|
||||||
|
_ => Err(sea_orm::sea_query::ValueTypeErr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_name() -> String {
|
||||||
|
format!("JsonVec_{}", T::type_name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn array_type() -> sea_orm::sea_query::ArrayType {
|
||||||
|
sea_orm::sea_query::ArrayType::Json
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_type() -> sea_orm::sea_query::ColumnType {
|
||||||
|
sea_orm::sea_query::ColumnType::Json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> sea_orm::sea_query::Nullable for JsonVec<T> {
|
||||||
|
fn null() -> sea_orm::Value {
|
||||||
|
sea_orm::Value::Json(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TypeName {
|
||||||
|
fn type_name() -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeName for String {
|
||||||
|
fn type_name() -> String {
|
||||||
|
"String".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use sea_orm::{ActiveValue::{NotSet, Set}, DatabaseConnection, EntityTrait};
|
use sea_orm::{ActiveValue::{NotSet, Set}, DatabaseConnection, EntityTrait};
|
||||||
|
|
||||||
use crate::model;
|
use crate::{ext::JsonVec, model};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum InitError {
|
pub enum InitError {
|
||||||
|
@ -34,9 +34,9 @@ pub async fn application(
|
||||||
domain: Set(domain.clone()),
|
domain: Set(domain.clone()),
|
||||||
preferred_username: Set(domain.clone()),
|
preferred_username: Set(domain.clone()),
|
||||||
actor_type: Set(apb::ActorType::Application),
|
actor_type: Set(apb::ActorType::Application),
|
||||||
also_known_as: Set(vec![]),
|
also_known_as: Set(JsonVec::default()),
|
||||||
moved_to: Set(None),
|
moved_to: Set(None),
|
||||||
fields: Set(vec![]), // TODO we could put some useful things here actually
|
fields: Set(JsonVec::default()), // TODO we could put some useful things here actually
|
||||||
private_key: Set(Some(privk)),
|
private_key: Set(Some(privk)),
|
||||||
public_key: Set(pubk),
|
public_key: Set(pubk),
|
||||||
following: Set(None),
|
following: Set(None),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use apb::{ActivityMut, ActivityType, BaseMut, ObjectMut};
|
use apb::{ActivityMut, ActivityType, BaseMut, ObjectMut};
|
||||||
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::model::Audience;
|
use crate::ext::JsonVec;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "activities")]
|
#[sea_orm(table_name = "activities")]
|
||||||
|
@ -14,10 +14,10 @@ pub struct Model {
|
||||||
pub actor: String,
|
pub actor: String,
|
||||||
pub object: Option<String>,
|
pub object: Option<String>,
|
||||||
pub target: Option<String>,
|
pub target: Option<String>,
|
||||||
pub to: Audience,
|
pub to: JsonVec<String>,
|
||||||
pub bto: Audience,
|
pub bto: JsonVec<String>,
|
||||||
pub cc: Audience,
|
pub cc: JsonVec<String>,
|
||||||
pub bcc: Audience,
|
pub bcc: JsonVec<String>,
|
||||||
pub published: ChronoDateTimeUtc,
|
pub published: ChronoDateTimeUtc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@ use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use apb::{field::OptionalString, ActorMut, ActorType, BaseMut, DocumentMut, EndpointsMut, ObjectMut, PublicKeyMut};
|
use apb::{field::OptionalString, ActorMut, ActorType, BaseMut, DocumentMut, EndpointsMut, ObjectMut, PublicKeyMut};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, sea_orm::FromJsonQueryResult)]
|
use crate::ext::{JsonVec, TypeName};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
@ -12,6 +14,12 @@ pub struct Field {
|
||||||
pub field_type: String,
|
pub field_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TypeName for Field {
|
||||||
|
fn type_name() -> String {
|
||||||
|
"Field".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: apb::Object> From<T> for Field {
|
impl<T: apb::Object> From<T> for Field {
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Field {
|
Field {
|
||||||
|
@ -37,7 +45,7 @@ pub struct Model {
|
||||||
pub image: Option<String>,
|
pub image: Option<String>,
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
pub preferred_username: String,
|
pub preferred_username: String,
|
||||||
pub fields: Vec<Field>,
|
pub fields: JsonVec<Field>,
|
||||||
pub inbox: Option<String>,
|
pub inbox: Option<String>,
|
||||||
pub shared_inbox: Option<String>,
|
pub shared_inbox: Option<String>,
|
||||||
pub outbox: Option<String>,
|
pub outbox: Option<String>,
|
||||||
|
@ -50,7 +58,7 @@ pub struct Model {
|
||||||
pub private_key: Option<String>,
|
pub private_key: Option<String>,
|
||||||
pub published: ChronoDateTimeUtc,
|
pub published: ChronoDateTimeUtc,
|
||||||
pub updated: ChronoDateTimeUtc,
|
pub updated: ChronoDateTimeUtc,
|
||||||
pub also_known_as: Vec<String>,
|
pub also_known_as: JsonVec<String>,
|
||||||
pub moved_to: Option<String>,
|
pub moved_to: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +216,7 @@ impl Model {
|
||||||
.set_url(apb::Node::link(i.clone()))
|
.set_url(apb::Node::link(i.clone()))
|
||||||
)))
|
)))
|
||||||
.set_attachment(apb::Node::array(
|
.set_attachment(apb::Node::array(
|
||||||
self.fields
|
self.fields.0
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|x| serde_json::to_value(x).ok())
|
.filter_map(|x| serde_json::to_value(x).ok())
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -233,7 +241,7 @@ impl Model {
|
||||||
apb::new()
|
apb::new()
|
||||||
.set_shared_inbox(self.shared_inbox.as_deref())
|
.set_shared_inbox(self.shared_inbox.as_deref())
|
||||||
))
|
))
|
||||||
.set_also_known_as(apb::Node::links(self.also_known_as))
|
.set_also_known_as(apb::Node::links(self.also_known_as.0))
|
||||||
.set_moved_to(apb::Node::maybe_link(self.moved_to))
|
.set_moved_to(apb::Node::maybe_link(self.moved_to))
|
||||||
.set_discoverable(Some(true))
|
.set_discoverable(Some(true))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,23 +19,3 @@ pub mod dislike;
|
||||||
pub mod hashtag;
|
pub mod hashtag;
|
||||||
pub mod mention;
|
pub mod mention;
|
||||||
pub mod attachment;
|
pub mod attachment;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, sea_orm::FromJsonQueryResult)]
|
|
||||||
pub struct Audience(pub Vec<String>);
|
|
||||||
|
|
||||||
impl<T: apb::Base> From<apb::Node<T>> for Audience {
|
|
||||||
fn from(value: apb::Node<T>) -> Self {
|
|
||||||
use apb::field::OptionalString;
|
|
||||||
|
|
||||||
Audience(
|
|
||||||
match value {
|
|
||||||
apb::Node::Empty => vec![],
|
|
||||||
apb::Node::Link(l) => l.href().map(|x| vec![x.to_string()]).unwrap_or_default(),
|
|
||||||
apb::Node::Object(o) => if let Ok(id) = o.id() { vec![id.to_string()] } else { vec![] },
|
|
||||||
apb::Node::Array(arr) => arr.into_iter().filter_map(|l| l.id().str()).collect(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use apb::{BaseMut, CollectionMut, DocumentMut, ObjectMut, ObjectType};
|
use apb::{BaseMut, CollectionMut, DocumentMut, ObjectMut, ObjectType};
|
||||||
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use super::Audience;
|
use crate::ext::JsonVec;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "objects")]
|
#[sea_orm(table_name = "objects")]
|
||||||
|
@ -24,10 +24,10 @@ pub struct Model {
|
||||||
pub announces: i32,
|
pub announces: i32,
|
||||||
pub replies: i32,
|
pub replies: i32,
|
||||||
pub context: Option<String>,
|
pub context: Option<String>,
|
||||||
pub to: Audience,
|
pub to: JsonVec<String>,
|
||||||
pub bto: Audience,
|
pub bto: JsonVec<String>,
|
||||||
pub cc: Audience,
|
pub cc: JsonVec<String>,
|
||||||
pub bcc: Audience,
|
pub bcc: JsonVec<String>,
|
||||||
pub published: ChronoDateTimeUtc,
|
pub published: ChronoDateTimeUtc,
|
||||||
pub updated: ChronoDateTimeUtc,
|
pub updated: ChronoDateTimeUtc,
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use sea_orm::{ActiveValue::{NotSet, Set}, DbErr, EntityTrait};
|
use sea_orm::{ActiveValue::{NotSet, Set}, DbErr, EntityTrait};
|
||||||
|
|
||||||
|
use crate::ext::JsonVec;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Administrable {
|
pub trait Administrable {
|
||||||
async fn register_user(
|
async fn register_user(
|
||||||
|
@ -35,13 +37,13 @@ impl Administrable for crate::Context {
|
||||||
domain: Set(domain),
|
domain: Set(domain),
|
||||||
summary: Set(summary),
|
summary: Set(summary),
|
||||||
preferred_username: Set(username.clone()),
|
preferred_username: Set(username.clone()),
|
||||||
fields: Set(vec![]),
|
fields: Set(JsonVec::default()),
|
||||||
following: Set(None),
|
following: Set(None),
|
||||||
following_count: Set(0),
|
following_count: Set(0),
|
||||||
followers: Set(None),
|
followers: Set(None),
|
||||||
followers_count: Set(0),
|
followers_count: Set(0),
|
||||||
statuses_count: Set(0),
|
statuses_count: Set(0),
|
||||||
also_known_as: Set(vec![]),
|
also_known_as: Set(JsonVec::default()),
|
||||||
moved_to: Set(None),
|
moved_to: Set(None),
|
||||||
icon: Set(avatar_url),
|
icon: Set(avatar_url),
|
||||||
image: Set(banner_url),
|
image: Set(banner_url),
|
||||||
|
|
|
@ -203,10 +203,10 @@ impl AP {
|
||||||
object: activity.object().id().str(),
|
object: activity.object().id().str(),
|
||||||
target: activity.target().id().str(),
|
target: activity.target().id().str(),
|
||||||
published: activity.published().unwrap_or(chrono::Utc::now()),
|
published: activity.published().unwrap_or(chrono::Utc::now()),
|
||||||
to: activity.to().into(),
|
to: activity.to().all_ids().into(),
|
||||||
bto: activity.bto().into(),
|
bto: activity.bto().all_ids().into(),
|
||||||
cc: activity.cc().into(),
|
cc: activity.cc().all_ids().into(),
|
||||||
bcc: activity.bcc().into(),
|
bcc: activity.bcc().all_ids().into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,10 +287,10 @@ impl AP {
|
||||||
announces: object.shares().get()
|
announces: object.shares().get()
|
||||||
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32,
|
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32,
|
||||||
audience: object.audience().id().str(),
|
audience: object.audience().id().str(),
|
||||||
to: object.to().into(),
|
to: object.to().all_ids().into(),
|
||||||
bto: object.bto().into(),
|
bto: object.bto().all_ids().into(),
|
||||||
cc: object.cc().into(),
|
cc: object.cc().all_ids().into(),
|
||||||
bcc: object.bcc().into(),
|
bcc: object.bcc().all_ids().into(),
|
||||||
|
|
||||||
sensitive: object.sensitive().unwrap_or(false),
|
sensitive: object.sensitive().unwrap_or(false),
|
||||||
})
|
})
|
||||||
|
@ -333,13 +333,12 @@ impl AP {
|
||||||
summary: actor.summary().str(),
|
summary: actor.summary().str(),
|
||||||
icon: actor.icon().get().and_then(|x| x.url().id().str()),
|
icon: actor.icon().get().and_then(|x| x.url().id().str()),
|
||||||
image: actor.image().get().and_then(|x| x.url().id().str()),
|
image: actor.image().get().and_then(|x| x.url().id().str()),
|
||||||
fields: actor.attachment().flat().into_iter().filter_map(|x| Some(crate::model::actor::Field::from(x.extract()?))).collect(),
|
|
||||||
inbox: actor.inbox().id().str(),
|
inbox: actor.inbox().id().str(),
|
||||||
outbox: actor.outbox().id().str(),
|
outbox: actor.outbox().id().str(),
|
||||||
shared_inbox: actor.endpoints().get().and_then(|x| x.shared_inbox().str()),
|
shared_inbox: actor.endpoints().get().and_then(|x| x.shared_inbox().str()),
|
||||||
followers: actor.followers().id().str(),
|
followers: actor.followers().id().str(),
|
||||||
following: actor.following().id().str(),
|
following: actor.following().id().str(),
|
||||||
also_known_as: actor.also_known_as().flat().into_iter().filter_map(|x| x.id().str()).collect(),
|
also_known_as: actor.also_known_as().flat().into_iter().filter_map(|x| x.id().str()).collect::<Vec<String>>().into(),
|
||||||
moved_to: actor.moved_to().id().str(),
|
moved_to: actor.moved_to().id().str(),
|
||||||
published: actor.published().unwrap_or(chrono::Utc::now()),
|
published: actor.published().unwrap_or(chrono::Utc::now()),
|
||||||
updated: chrono::Utc::now(),
|
updated: chrono::Utc::now(),
|
||||||
|
@ -348,6 +347,12 @@ impl AP {
|
||||||
statuses_count: actor.statuses_count().unwrap_or(0) as i32,
|
statuses_count: actor.statuses_count().unwrap_or(0) as i32,
|
||||||
public_key: actor.public_key().get().ok_or(apb::FieldErr("publicKey"))?.public_key_pem().to_string(),
|
public_key: actor.public_key().get().ok_or(apb::FieldErr("publicKey"))?.public_key_pem().to_string(),
|
||||||
private_key: None, // there's no way to transport privkey over AP json, must come from DB
|
private_key: None, // there's no way to transport privkey over AP json, must come from DB
|
||||||
|
fields: actor.attachment()
|
||||||
|
.flat()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| Some(crate::model::actor::Field::from(x.extract()?)))
|
||||||
|
.collect::<Vec<crate::model::actor::Field>>()
|
||||||
|
.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue