Skip to main content

upub/model/
actor.rs

1use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
2
3use apb::{ActorMut, ActorType, BaseMut, DocumentMut, EndpointsMut, ObjectMut, PublicKeyMut};
4
5use crate::ext::{JsonVec, TypeName};
6
7#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
8pub struct Field {
9	#[serde(default)]
10	pub name: String,
11	#[serde(default)]
12	pub value: String,
13	#[serde(default, skip_serializing_if = "Option::is_none")]
14	pub verified_at: Option<ChronoDateTimeUtc>,
15	#[serde(default, rename = "type")]
16	pub field_type: String,
17}
18
19impl TypeName for Field {
20	fn type_name() -> String {
21		"Field".to_string()
22	}
23}
24
25impl<T: apb::Object> From<T> for Field {
26	fn from(value: T) -> Self {
27		Field {
28			name: value.name().unwrap_or_default().to_string(),
29			value: mdhtml::safe_html(&value.value().unwrap_or_default()),
30			field_type: "PropertyValue".to_string(), // TODO can we try parsing this instead??
31			verified_at: None, // TODO where does verified_at come from? extend apb maybe
32		}
33	}
34}
35
36#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
37#[sea_orm(table_name = "actors")]
38pub struct Model {
39	#[sea_orm(primary_key)]
40	pub internal: i64,
41	#[sea_orm(unique)]
42	pub id: String,
43	pub actor_type: ActorType,
44	pub domain: String,
45	pub name: Option<String>,
46	pub summary: Option<String>,
47	pub image: Option<String>,
48	pub icon: Option<String>,
49	pub preferred_username: String,
50	pub fields: JsonVec<Field>,
51	pub inbox: Option<String>,
52	pub shared_inbox: Option<String>,
53	pub outbox: Option<String>,
54	pub following: Option<String>,
55	pub followers: Option<String>,
56	pub following_count: i32,
57	pub followers_count: i32,
58	pub statuses_count: i32,
59	pub public_key: String,
60	pub private_key: Option<String>,
61	pub published: ChronoDateTimeUtc,
62	pub updated: ChronoDateTimeUtc,
63	pub also_known_as: JsonVec<String>,
64	pub moved_to: Option<String>,
65}
66
67#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
68pub enum Relation {
69	#[sea_orm(has_many = "super::activity::Entity")]
70	Activities,
71	#[sea_orm(has_many = "super::addressing::Entity")]
72	Addressing,
73	#[sea_orm(has_many = "super::announce::Entity")]
74	Announces,
75	#[sea_orm(has_many = "super::config::Entity")]
76	Configs,
77	#[sea_orm(has_many = "super::credential::Entity")]
78	Credentials,
79	#[sea_orm(
80		belongs_to = "super::instance::Entity",
81		from = "Column::Domain",
82		to = "super::instance::Column::Domain",
83		on_update = "Cascade",
84		on_delete = "NoAction"
85	)]
86	Instances,
87	#[sea_orm(has_many = "super::dislike::Entity")]
88	Dislikes,
89	#[sea_orm(has_many = "super::like::Entity")]
90	Likes,
91	#[sea_orm(has_many = "super::mention::Entity")]
92	Mentions,
93	#[sea_orm(has_many = "super::notification::Entity")]
94	Notifications,
95	#[sea_orm(has_many = "super::object::Entity")]
96	Objects,
97	#[sea_orm(has_many = "super::relation::Entity")]
98	Relations,
99	#[sea_orm(has_many = "super::session::Entity")]
100	Sessions,
101}
102
103impl Related<super::activity::Entity> for Entity {
104	fn to() -> RelationDef {
105		Relation::Activities.def()
106	}
107}
108
109impl Related<super::addressing::Entity> for Entity {
110	fn to() -> RelationDef {
111		Relation::Addressing.def()
112	}
113}
114
115impl Related<super::announce::Entity> for Entity {
116	fn to() -> RelationDef {
117		Relation::Announces.def()
118	}
119}
120
121impl Related<super::config::Entity> for Entity {
122	fn to() -> RelationDef {
123		Relation::Configs.def()
124	}
125}
126
127impl Related<super::credential::Entity> for Entity {
128	fn to() -> RelationDef {
129		Relation::Credentials.def()
130	}
131}
132
133impl Related<super::instance::Entity> for Entity {
134	fn to() -> RelationDef {
135		Relation::Instances.def()
136	}
137}
138
139impl Related<super::dislike::Entity> for Entity {
140	fn to() -> RelationDef {
141		Relation::Dislikes.def()
142	}
143}
144
145impl Related<super::like::Entity> for Entity {
146	fn to() -> RelationDef {
147		Relation::Likes.def()
148	}
149}
150
151impl Related<super::mention::Entity> for Entity {
152	fn to() -> RelationDef {
153		Relation::Mentions.def()
154	}
155}
156
157impl Related<super::notification::Entity> for Entity {
158	fn to() -> RelationDef {
159		Relation::Notifications.def()
160	}
161}
162
163impl Related<super::object::Entity> for Entity {
164	fn to() -> RelationDef {
165		Relation::Objects.def()
166	}
167}
168
169impl Related<super::relation::Entity> for Entity {
170	fn to() -> RelationDef {
171		Relation::Relations.def()
172	}
173}
174
175impl Related<super::session::Entity> for Entity {
176	fn to() -> RelationDef {
177		Relation::Sessions.def()
178	}
179}
180
181impl ActiveModelBehavior for ActiveModel {}
182
183impl Entity {
184	pub fn find_by_ap_id(id: &str) -> Select<Entity> {
185		Entity::find().filter(Column::Id.eq(id))
186	}
187
188	pub fn delete_by_ap_id(id: &str) -> sea_orm::DeleteMany<Entity> {
189		Entity::delete_many().filter(Column::Id.eq(id))
190	}
191
192	pub async fn ap_to_internal(id: &str, db: &impl ConnectionTrait) -> Result<Option<i64>, DbErr> {
193		Entity::find()
194			.filter(Column::Id.eq(id))
195			.select_only()
196			.select_column(Column::Internal)
197			.into_tuple::<i64>()
198			.one(db)
199			.await
200	}
201}
202
203impl crate::ext::IntoActivityPub for Model {
204	fn into_activity_pub_json(self, ctx: &crate::Context) -> serde_json::Value {
205		let is_local = ctx.is_local(&self.id);
206		let id = ctx.id(&self.id);
207		apb::new()
208			.set_id(Some(self.id.clone()))
209			.set_actor_type(Some(self.actor_type))
210			.set_name(self.name)
211			.set_summary(self.summary)
212			.set_icon(apb::Node::maybe_object(self.icon.map(|i|
213				apb::new()
214					.set_document_type(Some(apb::DocumentType::Image))
215					.set_url(apb::Node::link(i.clone()))
216			)))
217			.set_image(apb::Node::maybe_object(self.image.map(|i|
218				apb::new()
219					.set_document_type(Some(apb::DocumentType::Image))
220					.set_url(apb::Node::link(i.clone()))
221			)))
222			.set_attachment(apb::Node::array(
223				self.fields.0
224					.into_iter()
225					.filter_map(|x| serde_json::to_value(x).ok())
226					.collect()
227			))
228			.set_published(Some(self.published))
229			.set_updated(if self.updated != self.published { Some(self.updated) } else { None })
230			.set_preferred_username(Some(self.preferred_username))
231			.set_statuses_count(Some(self.statuses_count as u64))
232			// local users may want to hide these! default to hidden, and downstream we can opt-in to
233			// showing them. for remote users we assume the number is already "protected" by remote
234			// instance so we just show it
235			.set_followers_count(if is_local { None } else { Some(self.followers_count as u64) })
236			.set_following_count(if is_local { None } else { Some(self.following_count as u64) })
237			.set_inbox(if is_local { apb::Node::link(crate::url!(ctx, "/actors/{id}/inbox")) } else { apb::Node::maybe_link(self.inbox) })
238			.set_outbox(if is_local { apb::Node::link(crate::url!(ctx, "/actors/{id}/outbox")) } else { apb::Node::maybe_link(self.outbox) })
239			.set_following(if is_local { apb::Node::link(crate::url!(ctx, "/actors/{id}/following")) } else { apb::Node::maybe_link(self.following) })
240			.set_followers(if is_local { apb::Node::link(crate::url!(ctx, "/actors/{id}/followers")) } else { apb::Node::maybe_link(self.followers) })
241			.set_public_key(apb::Node::object(
242				apb::new()
243					.set_id(Some(format!("{}#main-key", self.id)))
244					.set_owner(Some(self.id))
245					.set_public_key_pem(self.public_key)
246			))
247			.set_endpoints(apb::Node::object(
248				apb::new()
249					.set_shared_inbox(if is_local { Some(crate::url!(ctx, "/inbox")) } else { self.shared_inbox })
250					.set_proxy_url(if is_local { Some(crate::url!(ctx, "/fetch")) } else { None })
251			))
252			.set_also_known_as(apb::Node::links(self.also_known_as.0))
253			.set_moved_to(apb::Node::maybe_link(self.moved_to))
254			.set_discoverable(Some(true))
255	}
256}