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(), verified_at: None, }
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 .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}