upub/core/src/model/actor.rs

257 lines
7.2 KiB
Rust
Raw Normal View History

use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
use apb::{ActorMut, ActorType, BaseMut, DocumentMut, EndpointsMut, ObjectMut, PublicKeyMut};
use crate::ext::{JsonVec, TypeName};
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Field {
#[serde(default)]
pub name: String,
#[serde(default)]
2024-07-15 21:15:29 +02:00
pub value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub verified_at: Option<ChronoDateTimeUtc>,
#[serde(default, rename = "type")]
pub field_type: String,
}
impl TypeName for Field {
fn type_name() -> String {
"Field".to_string()
}
}
impl<T: apb::Object> From<T> for Field {
fn from(value: T) -> Self {
Field {
name: value.name().unwrap_or_default().to_string(),
value: mdhtml::safe_html(&value.value().unwrap_or_default()),
field_type: "PropertyValue".to_string(), // TODO can we try parsing this instead??
verified_at: None, // TODO where does verified_at come from? extend apb maybe
}
}
}
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "actors")]
pub struct Model {
#[sea_orm(primary_key)]
pub internal: i64,
#[sea_orm(unique)]
pub id: String,
pub actor_type: ActorType,
pub domain: String,
pub name: Option<String>,
2024-03-21 01:09:33 +01:00
pub summary: Option<String>,
pub image: Option<String>,
pub icon: Option<String>,
pub preferred_username: String,
pub fields: JsonVec<Field>,
2024-03-21 01:09:33 +01:00
pub inbox: Option<String>,
pub shared_inbox: Option<String>,
pub outbox: Option<String>,
pub following: Option<String>,
pub followers: Option<String>,
pub following_count: i32,
pub followers_count: i32,
pub statuses_count: i32,
pub public_key: String,
pub private_key: Option<String>,
pub published: ChronoDateTimeUtc,
2024-03-21 01:09:33 +01:00
pub updated: ChronoDateTimeUtc,
pub also_known_as: JsonVec<String>,
pub moved_to: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::activity::Entity")]
Activities,
#[sea_orm(has_many = "super::addressing::Entity")]
Addressing,
#[sea_orm(has_many = "super::announce::Entity")]
Announces,
#[sea_orm(has_many = "super::config::Entity")]
Configs,
#[sea_orm(has_many = "super::credential::Entity")]
Credentials,
#[sea_orm(
belongs_to = "super::instance::Entity",
from = "Column::Domain",
to = "super::instance::Column::Domain",
on_update = "Cascade",
on_delete = "NoAction"
)]
Instances,
#[sea_orm(has_many = "super::dislike::Entity")]
Dislikes,
#[sea_orm(has_many = "super::like::Entity")]
Likes,
#[sea_orm(has_many = "super::mention::Entity")]
Mentions,
#[sea_orm(has_many = "super::notification::Entity")]
Notifications,
#[sea_orm(has_many = "super::object::Entity")]
Objects,
#[sea_orm(has_many = "super::relation::Entity")]
Relations,
#[sea_orm(has_many = "super::session::Entity")]
Sessions,
}
impl Related<super::activity::Entity> for Entity {
fn to() -> RelationDef {
Relation::Activities.def()
}
}
impl Related<super::addressing::Entity> for Entity {
fn to() -> RelationDef {
Relation::Addressing.def()
}
}
impl Related<super::announce::Entity> for Entity {
fn to() -> RelationDef {
Relation::Announces.def()
}
}
impl Related<super::config::Entity> for Entity {
fn to() -> RelationDef {
Relation::Configs.def()
}
}
impl Related<super::credential::Entity> for Entity {
fn to() -> RelationDef {
Relation::Credentials.def()
}
}
impl Related<super::instance::Entity> for Entity {
fn to() -> RelationDef {
Relation::Instances.def()
}
}
impl Related<super::dislike::Entity> for Entity {
fn to() -> RelationDef {
Relation::Dislikes.def()
}
}
impl Related<super::like::Entity> for Entity {
fn to() -> RelationDef {
Relation::Likes.def()
}
}
impl Related<super::mention::Entity> for Entity {
fn to() -> RelationDef {
Relation::Mentions.def()
}
}
impl Related<super::notification::Entity> for Entity {
fn to() -> RelationDef {
Relation::Notifications.def()
}
}
impl Related<super::object::Entity> for Entity {
fn to() -> RelationDef {
Relation::Objects.def()
}
}
impl Related<super::relation::Entity> for Entity {
fn to() -> RelationDef {
Relation::Relations.def()
}
}
impl Related<super::session::Entity> for Entity {
fn to() -> RelationDef {
Relation::Sessions.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
impl Entity {
pub fn find_by_ap_id(id: &str) -> Select<Entity> {
Entity::find().filter(Column::Id.eq(id))
}
pub fn delete_by_ap_id(id: &str) -> sea_orm::DeleteMany<Entity> {
Entity::delete_many().filter(Column::Id.eq(id))
}
pub async fn ap_to_internal(id: &str, db: &impl ConnectionTrait) -> Result<Option<i64>, DbErr> {
Entity::find()
.filter(Column::Id.eq(id))
.select_only()
.select_column(Column::Internal)
.into_tuple::<i64>()
.one(db)
.await
}
}
impl crate::ext::IntoActivityPub for Model {
fn into_activity_pub_json(self, ctx: &crate::Context) -> serde_json::Value {
let is_local = ctx.is_local(&self.id);
let id = ctx.id(&self.id);
apb::new()
.set_id(Some(self.id.clone()))
.set_actor_type(Some(self.actor_type))
.set_name(self.name)
.set_summary(self.summary)
.set_icon(apb::Node::maybe_object(self.icon.map(|i|
apb::new()
.set_document_type(Some(apb::DocumentType::Image))
.set_url(apb::Node::link(i.clone()))
)))
.set_image(apb::Node::maybe_object(self.image.map(|i|
apb::new()
.set_document_type(Some(apb::DocumentType::Image))
.set_url(apb::Node::link(i.clone()))
)))
.set_attachment(apb::Node::array(
self.fields.0
.into_iter()
2024-07-15 14:03:22 +02:00
.filter_map(|x| serde_json::to_value(x).ok())
.collect()
))
.set_published(Some(self.published))
.set_updated(if self.updated != self.published { Some(self.updated) } else { None })
.set_preferred_username(Some(self.preferred_username))
2024-05-02 00:47:11 +02:00
.set_statuses_count(Some(self.statuses_count as u64))
// local users may want to hide these! default to hidden, and downstream we can opt-in to
// showing them. for remote users we assume the number is already "protected" by remote
// instance so we just show it
.set_followers_count(if is_local { None } else { Some(self.followers_count as u64) })
.set_following_count(if is_local { None } else { Some(self.following_count as u64) })
.set_inbox(if is_local { apb::Node::link(crate::url!(ctx, "/actors/{id}/inbox")) } else { apb::Node::maybe_link(self.inbox) })
.set_outbox(if is_local { apb::Node::link(crate::url!(ctx, "/actors/{id}/outbox")) } else { apb::Node::maybe_link(self.outbox) })
.set_following(if is_local { apb::Node::link(crate::url!(ctx, "/actors{id}/following")) } else { apb::Node::maybe_link(self.following) })
.set_followers(if is_local { apb::Node::link(crate::url!(ctx, "/actors/{id}/followers")) } else { apb::Node::maybe_link(self.followers) })
.set_public_key(apb::Node::object(
apb::new()
.set_id(Some(format!("{}#main-key", self.id)))
.set_owner(Some(self.id))
.set_public_key_pem(self.public_key)
))
.set_endpoints(apb::Node::object(
apb::new()
.set_shared_inbox(if is_local { Some(crate::url!(ctx, "/inbox")) } else { self.shared_inbox })
.set_proxy_url(if is_local { Some(crate::url!(ctx, "/fetch")) } else { None })
))
.set_also_known_as(apb::Node::links(self.also_known_as.0))
.set_moved_to(apb::Node::maybe_link(self.moved_to))
.set_discoverable(Some(true))
}
2024-03-23 06:32:15 +01:00
}