forked from alemi/upub
chore: updated models and some server components
This commit is contained in:
parent
94ec7d0d37
commit
b09cfd0526
15 changed files with 338 additions and 278 deletions
|
@ -1,7 +1,7 @@
|
||||||
use apb::{ActivityMut, ActivityType, BaseMut, ObjectMut};
|
use apb::{ActivityMut, ActivityType, BaseMut, ObjectMut};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::routes::activitypub::jsonld::LD;
|
use crate::{model::Audience, errors::UpubError, routes::activitypub::jsonld::LD};
|
||||||
|
|
||||||
#[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: Option<Json>,
|
pub to: Audience,
|
||||||
pub bto: Option<Json>,
|
pub bto: Audience,
|
||||||
pub cc: Option<Json>,
|
pub cc: Audience,
|
||||||
pub bcc: Option<Json>,
|
pub bcc: Audience,
|
||||||
pub published: ChronoDateTimeUtc,
|
pub published: ChronoDateTimeUtc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +71,28 @@ impl Related<super::object::Entity> for Entity {
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
impl Entity {
|
||||||
|
pub fn find_by_ap_id(id: &str) -> Select<Entity> {
|
||||||
|
Entity::find().filter(Column::Id.eq(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ap_to_internal(id: &str, db: &DatabaseConnection) -> crate::Result<i64> {
|
||||||
|
Entity::find()
|
||||||
|
.filter(Column::Id.eq(id))
|
||||||
|
.select_only()
|
||||||
|
.select_column(Column::Internal)
|
||||||
|
.into_tuple::<i64>()
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(UpubError::not_found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModel {
|
impl ActiveModel {
|
||||||
pub fn new(activity: &impl apb::Activity) -> Result<Self, super::FieldError> {
|
pub fn new(activity: &impl apb::Activity) -> Result<Self, super::FieldError> {
|
||||||
Ok(ActiveModel {
|
Ok(ActiveModel {
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
ap_id: sea_orm::ActiveValue::Set(activity.id().ok_or(super::FieldError("id"))?.to_string()),
|
id: sea_orm::ActiveValue::Set(activity.id().ok_or(super::FieldError("id"))?.to_string()),
|
||||||
activity_type: sea_orm::ActiveValue::Set(activity.activity_type().ok_or(super::FieldError("type"))?),
|
activity_type: sea_orm::ActiveValue::Set(activity.activity_type().ok_or(super::FieldError("type"))?),
|
||||||
actor: sea_orm::ActiveValue::Set(activity.actor().id().ok_or(super::FieldError("actor"))?),
|
actor: sea_orm::ActiveValue::Set(activity.actor().id().ok_or(super::FieldError("actor"))?),
|
||||||
object: sea_orm::ActiveValue::Set(activity.object().id()),
|
object: sea_orm::ActiveValue::Set(activity.object().id()),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use apb::{Actor, ActorMut, ActorType, BaseMut, DocumentMut, Endpoints, EndpointsMut, Object, ObjectMut, PublicKey, PublicKeyMut};
|
use apb::{Actor, ActorMut, ActorType, BaseMut, DocumentMut, Endpoints, EndpointsMut, Object, ObjectMut, PublicKey, PublicKeyMut};
|
||||||
|
|
||||||
use crate::routes::activitypub::jsonld::LD;
|
use crate::{errors::UpubError, routes::activitypub::jsonld::LD};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "actors")]
|
#[sea_orm(table_name = "actors")]
|
||||||
|
@ -133,14 +133,31 @@ impl Related<super::session::Entity> for Entity {
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
impl Entity {
|
||||||
|
pub fn find_by_ap_id(id: &str) -> Select<Entity> {
|
||||||
|
Entity::find().filter(Column::Id.eq(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ap_to_internal(id: &str, db: &DatabaseConnection) -> crate::Result<i64> {
|
||||||
|
Entity::find()
|
||||||
|
.filter(Column::Id.eq(id))
|
||||||
|
.select_only()
|
||||||
|
.select_column(Column::Internal)
|
||||||
|
.into_tuple::<i64>()
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(UpubError::not_found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModel {
|
impl ActiveModel {
|
||||||
pub fn new(object: &impl Actor, instance: i32) -> Result<Self, super::FieldError> {
|
pub fn new(object: &impl Actor) -> Result<Self, super::FieldError> {
|
||||||
let ap_id = object.id().ok_or(super::FieldError("id"))?.to_string();
|
let ap_id = object.id().ok_or(super::FieldError("id"))?.to_string();
|
||||||
let (_domain, fallback_preferred_username) = split_user_id(&ap_id);
|
let (domain, fallback_preferred_username) = split_user_id(&ap_id);
|
||||||
Ok(ActiveModel {
|
Ok(ActiveModel {
|
||||||
instance: sea_orm::ActiveValue::Set(instance), // TODO receiving it from outside is cheap
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
domain: sea_orm::ActiveValue::Set(domain),
|
||||||
ap_id: sea_orm::ActiveValue::Set(ap_id),
|
id: sea_orm::ActiveValue::Set(ap_id),
|
||||||
preferred_username: sea_orm::ActiveValue::Set(object.preferred_username().unwrap_or(&fallback_preferred_username).to_string()),
|
preferred_username: sea_orm::ActiveValue::Set(object.preferred_username().unwrap_or(&fallback_preferred_username).to_string()),
|
||||||
actor_type: sea_orm::ActiveValue::Set(object.actor_type().ok_or(super::FieldError("type"))?),
|
actor_type: sea_orm::ActiveValue::Set(object.actor_type().ok_or(super::FieldError("type"))?),
|
||||||
name: sea_orm::ActiveValue::Set(object.name().map(|x| x.to_string())),
|
name: sea_orm::ActiveValue::Set(object.name().map(|x| x.to_string())),
|
||||||
|
@ -154,9 +171,9 @@ impl ActiveModel {
|
||||||
following: sea_orm::ActiveValue::Set(object.following().id()),
|
following: sea_orm::ActiveValue::Set(object.following().id()),
|
||||||
created: sea_orm::ActiveValue::Set(object.published().unwrap_or(chrono::Utc::now())),
|
created: sea_orm::ActiveValue::Set(object.published().unwrap_or(chrono::Utc::now())),
|
||||||
updated: sea_orm::ActiveValue::Set(chrono::Utc::now()),
|
updated: sea_orm::ActiveValue::Set(chrono::Utc::now()),
|
||||||
following_count: sea_orm::ActiveValue::Set(object.following_count().unwrap_or(0) as i64),
|
following_count: sea_orm::ActiveValue::Set(object.following_count().unwrap_or(0) as i32),
|
||||||
followers_count: sea_orm::ActiveValue::Set(object.followers_count().unwrap_or(0) as i64),
|
followers_count: sea_orm::ActiveValue::Set(object.followers_count().unwrap_or(0) as i32),
|
||||||
statuses_count: sea_orm::ActiveValue::Set(object.statuses_count().unwrap_or(0) as i64),
|
statuses_count: sea_orm::ActiveValue::Set(object.statuses_count().unwrap_or(0) as i32),
|
||||||
public_key: sea_orm::ActiveValue::Set(object.public_key().get().ok_or(super::FieldError("publicKey"))?.public_key_pem().to_string()),
|
public_key: sea_orm::ActiveValue::Set(object.public_key().get().ok_or(super::FieldError("publicKey"))?.public_key_pem().to_string()),
|
||||||
private_key: sea_orm::ActiveValue::Set(None), // there's no way to transport privkey over AP json, must come from DB
|
private_key: sea_orm::ActiveValue::Set(None), // there's no way to transport privkey over AP json, must come from DB
|
||||||
})
|
})
|
||||||
|
|
|
@ -154,12 +154,12 @@ impl FromQueryResult for Event {
|
||||||
|
|
||||||
|
|
||||||
impl Entity {
|
impl Entity {
|
||||||
pub fn find_addressed(uid: Option<&str>) -> Select<Entity> {
|
pub fn find_addressed(uid: Option<i64>) -> Select<Entity> {
|
||||||
let mut select = Entity::find()
|
let mut select = Entity::find()
|
||||||
.distinct()
|
.distinct()
|
||||||
.select_only()
|
.select_only()
|
||||||
.join(sea_orm::JoinType::LeftJoin, Relation::Object.def())
|
.join(sea_orm::JoinType::LeftJoin, Relation::Objects.def())
|
||||||
.join(sea_orm::JoinType::LeftJoin, Relation::Activity.def())
|
.join(sea_orm::JoinType::LeftJoin, Relation::Activities.def())
|
||||||
.filter(
|
.filter(
|
||||||
// TODO ghetto double inner join because i want to filter out tombstones
|
// TODO ghetto double inner join because i want to filter out tombstones
|
||||||
Condition::any()
|
Condition::any()
|
||||||
|
@ -169,12 +169,11 @@ impl Entity {
|
||||||
.order_by(Column::Published, Order::Desc);
|
.order_by(Column::Published, Order::Desc);
|
||||||
|
|
||||||
if let Some(uid) = uid {
|
if let Some(uid) = uid {
|
||||||
let uid = uid.to_string();
|
|
||||||
select = select
|
select = select
|
||||||
.join(
|
.join(
|
||||||
sea_orm::JoinType::LeftJoin,
|
sea_orm::JoinType::LeftJoin,
|
||||||
crate::model::object::Relation::Like.def()
|
crate::model::object::Relation::Likes.def()
|
||||||
.on_condition(move |_l, _r| crate::model::like::Column::Actor.eq(uid.clone()).into_condition()),
|
.on_condition(move |_l, _r| crate::model::like::Column::Actor.eq(uid).into_condition()),
|
||||||
)
|
)
|
||||||
.select_column_as(crate::model::like::Column::Actor, format!("{}{}", crate::model::like::Entity.table_name(), crate::model::like::Column::Actor.to_string()));
|
.select_column_as(crate::model::like::Column::Actor, format!("{}{}", crate::model::like::Entity.table_name(), crate::model::like::Column::Actor.to_string()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use apb::{DocumentMut, ObjectMut};
|
use apb::{DocumentMut, DocumentType, ObjectMut};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
use crate::routes::activitypub::jsonld::LD;
|
use crate::routes::activitypub::jsonld::LD;
|
||||||
|
@ -13,7 +13,7 @@ pub struct Model {
|
||||||
#[sea_orm(unique)]
|
#[sea_orm(unique)]
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub object: i64,
|
pub object: i64,
|
||||||
pub document_type: String,
|
pub document_type: DocumentType,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub media_type: String,
|
pub media_type: String,
|
||||||
pub created: ChronoDateTimeUtc,
|
pub created: ChronoDateTimeUtc,
|
||||||
|
@ -52,12 +52,12 @@ impl Model {
|
||||||
|
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
pub trait BatchFillable {
|
pub trait BatchFillable {
|
||||||
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<String, Vec<Model>>, DbErr>;
|
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<i64, Vec<Model>>, DbErr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
impl BatchFillable for &[Event] {
|
impl BatchFillable for &[Event] {
|
||||||
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<String, Vec<Model>>, DbErr> {
|
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<i64, Vec<Model>>, DbErr> {
|
||||||
let objects : Vec<crate::model::object::Model> = self
|
let objects : Vec<crate::model::object::Model> = self
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|x| match x {
|
.filter_map(|x| match x {
|
||||||
|
@ -70,12 +70,12 @@ impl BatchFillable for &[Event] {
|
||||||
|
|
||||||
let attachments = objects.load_many(Entity, db).await?;
|
let attachments = objects.load_many(Entity, db).await?;
|
||||||
|
|
||||||
let mut out : std::collections::BTreeMap<String, Vec<Model>> = std::collections::BTreeMap::new();
|
let mut out : std::collections::BTreeMap<i64, Vec<Model>> = std::collections::BTreeMap::new();
|
||||||
for attach in attachments.into_iter().flatten() {
|
for attach in attachments.into_iter().flatten() {
|
||||||
if out.contains_key(&attach.object) {
|
if out.contains_key(&attach.object) {
|
||||||
out.get_mut(&attach.object).expect("contains but get failed?").push(attach);
|
out.get_mut(&attach.object).expect("contains but get failed?").push(attach);
|
||||||
} else {
|
} else {
|
||||||
out.insert(attach.object.clone(), vec![attach]);
|
out.insert(attach.object, vec![attach]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,14 +85,14 @@ impl BatchFillable for &[Event] {
|
||||||
|
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
impl BatchFillable for Vec<Event> {
|
impl BatchFillable for Vec<Event> {
|
||||||
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<String, Vec<Model>>, DbErr> {
|
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<i64, Vec<Model>>, DbErr> {
|
||||||
self.as_slice().load_attachments_batch(db).await
|
self.as_slice().load_attachments_batch(db).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
impl BatchFillable for Event {
|
impl BatchFillable for Event {
|
||||||
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<String, Vec<Model>>, DbErr> {
|
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<i64, Vec<Model>>, DbErr> {
|
||||||
let x = vec![self.clone()]; // TODO wasteful clone and vec![] but ehhh convenient
|
let x = vec![self.clone()]; // TODO wasteful clone and vec![] but ehhh convenient
|
||||||
x.load_attachments_batch(db).await
|
x.load_attachments_batch(db).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
|
use crate::errors::UpubError;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
#[sea_orm(table_name = "instances")]
|
#[sea_orm(table_name = "instances")]
|
||||||
|
@ -39,3 +41,20 @@ impl Related<super::addressing::Entity> for Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
impl Entity {
|
||||||
|
pub fn find_by_domain(domain: &str) -> Select<Entity> {
|
||||||
|
Entity::find().filter(Column::Domain.eq(domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn domain_to_internal(domain: &str, db: &DatabaseConnection) -> crate::Result<i64> {
|
||||||
|
Entity::find()
|
||||||
|
.filter(Column::Domain.eq(domain))
|
||||||
|
.select_only()
|
||||||
|
.select_column(Column::Internal)
|
||||||
|
.into_tuple::<i64>()
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(UpubError::not_found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use apb::{BaseMut, Collection, CollectionMut, ObjectMut};
|
use apb::{BaseMut, Collection, CollectionMut, ObjectMut, ObjectType};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::routes::activitypub::jsonld::LD;
|
use crate::{errors::UpubError, routes::activitypub::jsonld::LD};
|
||||||
|
|
||||||
use super::Audience;
|
use super::Audience;
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ pub struct Model {
|
||||||
pub internal: i64,
|
pub internal: i64,
|
||||||
#[sea_orm(unique)]
|
#[sea_orm(unique)]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub object_type: String,
|
pub object_type: ObjectType,
|
||||||
pub attributed_to: Option<String>,
|
pub attributed_to: Option<String>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub summary: Option<String>,
|
pub summary: Option<String>,
|
||||||
|
@ -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: Option<Json>,
|
pub to: Audience,
|
||||||
pub bto: Option<Json>,
|
pub bto: Audience,
|
||||||
pub cc: Option<Json>,
|
pub cc: Audience,
|
||||||
pub bcc: Option<Json>,
|
pub bcc: Audience,
|
||||||
pub published: ChronoDateTimeUtc,
|
pub published: ChronoDateTimeUtc,
|
||||||
pub updated: ChronoDateTimeUtc,
|
pub updated: ChronoDateTimeUtc,
|
||||||
}
|
}
|
||||||
|
@ -122,11 +122,28 @@ impl Related<Entity> for Entity {
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
impl Entity {
|
||||||
|
pub fn find_by_ap_id(id: &str) -> Select<Entity> {
|
||||||
|
Entity::find().filter(Column::Id.eq(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ap_to_internal(id: &str, db: &DatabaseConnection) -> crate::Result<i64> {
|
||||||
|
Entity::find()
|
||||||
|
.filter(Column::Id.eq(id))
|
||||||
|
.select_only()
|
||||||
|
.select_column(Column::Internal)
|
||||||
|
.into_tuple::<i64>()
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(UpubError::not_found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModel {
|
impl ActiveModel {
|
||||||
pub fn new(object: &impl apb::Object) -> Result<Self, super::FieldError> {
|
pub fn new(object: &impl apb::Object) -> Result<Self, super::FieldError> {
|
||||||
Ok(ActiveModel {
|
Ok(ActiveModel {
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
ap_id: sea_orm::ActiveValue::Set(object.id().ok_or(super::FieldError("id"))?.to_string()),
|
id: sea_orm::ActiveValue::Set(object.id().ok_or(super::FieldError("id"))?.to_string()),
|
||||||
object_type: sea_orm::ActiveValue::Set(object.object_type().ok_or(super::FieldError("type"))?),
|
object_type: sea_orm::ActiveValue::Set(object.object_type().ok_or(super::FieldError("type"))?),
|
||||||
attributed_to: sea_orm::ActiveValue::Set(object.attributed_to().id()),
|
attributed_to: sea_orm::ActiveValue::Set(object.attributed_to().id()),
|
||||||
name: sea_orm::ActiveValue::Set(object.name().map(|x| x.to_string())),
|
name: sea_orm::ActiveValue::Set(object.name().map(|x| x.to_string())),
|
||||||
|
@ -135,14 +152,14 @@ impl ActiveModel {
|
||||||
context: sea_orm::ActiveValue::Set(object.context().id()),
|
context: sea_orm::ActiveValue::Set(object.context().id()),
|
||||||
in_reply_to: sea_orm::ActiveValue::Set(object.in_reply_to().id()),
|
in_reply_to: sea_orm::ActiveValue::Set(object.in_reply_to().id()),
|
||||||
published: sea_orm::ActiveValue::Set(object.published().ok_or(super::FieldError("published"))?),
|
published: sea_orm::ActiveValue::Set(object.published().ok_or(super::FieldError("published"))?),
|
||||||
updated: sea_orm::ActiveValue::Set(object.updated()),
|
updated: sea_orm::ActiveValue::Set(object.updated().unwrap_or_else(chrono::Utc::now)),
|
||||||
url: sea_orm::ActiveValue::Set(object.url().id()),
|
url: sea_orm::ActiveValue::Set(object.url().id()),
|
||||||
replies: sea_orm::ActiveValue::Set(object.replies().get()
|
replies: sea_orm::ActiveValue::Set(object.replies().get()
|
||||||
.map_or(0, |x| x.total_items().unwrap_or(0)) as i64),
|
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32),
|
||||||
likes: sea_orm::ActiveValue::Set(object.likes().get()
|
likes: sea_orm::ActiveValue::Set(object.likes().get()
|
||||||
.map_or(0, |x| x.total_items().unwrap_or(0)) as i64),
|
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32),
|
||||||
announces: sea_orm::ActiveValue::Set(object.shares().get()
|
announces: sea_orm::ActiveValue::Set(object.shares().get()
|
||||||
.map_or(0, |x| x.total_items().unwrap_or(0)) as i64),
|
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32),
|
||||||
to: sea_orm::ActiveValue::Set(object.to().into()),
|
to: sea_orm::ActiveValue::Set(object.to().into()),
|
||||||
bto: sea_orm::ActiveValue::Set(object.bto().into()),
|
bto: sea_orm::ActiveValue::Set(object.bto().into()),
|
||||||
cc: sea_orm::ActiveValue::Set(object.cc().into()),
|
cc: sea_orm::ActiveValue::Set(object.cc().into()),
|
||||||
|
@ -176,7 +193,7 @@ impl Model {
|
||||||
.set_shares(apb::Node::object(
|
.set_shares(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
serde_json::Value::new_object()
|
||||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||||
.set_total_items(Some(self.shares as u64))
|
.set_total_items(Some(self.announces as u64))
|
||||||
))
|
))
|
||||||
.set_likes(apb::Node::object(
|
.set_likes(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
serde_json::Value::new_object()
|
||||||
|
@ -186,7 +203,7 @@ impl Model {
|
||||||
.set_replies(apb::Node::object(
|
.set_replies(apb::Node::object(
|
||||||
serde_json::Value::new_object()
|
serde_json::Value::new_object()
|
||||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||||
.set_total_items(Some(self.comments as u64))
|
.set_total_items(Some(self.replies as u64))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub async fn login(
|
||||||
// TODO salt the pwd
|
// TODO salt the pwd
|
||||||
match model::credential::Entity::find()
|
match model::credential::Entity::find()
|
||||||
.filter(Condition::all()
|
.filter(Condition::all()
|
||||||
.add(model::credential::Column::Email.eq(login.email))
|
.add(model::credential::Column::Login.eq(login.email))
|
||||||
.add(model::credential::Column::Password.eq(sha256::digest(login.password)))
|
.add(model::credential::Column::Password.eq(sha256::digest(login.password)))
|
||||||
)
|
)
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
|
@ -41,8 +41,9 @@ pub async fn login(
|
||||||
let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6);
|
let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6);
|
||||||
model::session::Entity::insert(
|
model::session::Entity::insert(
|
||||||
model::session::ActiveModel {
|
model::session::ActiveModel {
|
||||||
id: sea_orm::ActiveValue::Set(token.clone()),
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
actor: sea_orm::ActiveValue::Set(x.id.clone()),
|
secret: sea_orm::ActiveValue::Set(token.clone()),
|
||||||
|
actor: sea_orm::ActiveValue::Set(x.actor.clone()),
|
||||||
expires: sea_orm::ActiveValue::Set(expires),
|
expires: sea_orm::ActiveValue::Set(expires),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -50,7 +51,7 @@ pub async fn login(
|
||||||
.await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
Ok(Json(AuthSuccess {
|
Ok(Json(AuthSuccess {
|
||||||
token, expires,
|
token, expires,
|
||||||
user: x.id
|
user: x.actor
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
None => Err(UpubError::unauthorized()),
|
None => Err(UpubError::unauthorized()),
|
||||||
|
|
|
@ -2,7 +2,7 @@ pub mod replies;
|
||||||
|
|
||||||
use apb::{CollectionMut, ObjectMut};
|
use apb::{CollectionMut, ObjectMut};
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter, QuerySelect, SelectColumns};
|
use sea_orm::{ColumnTrait, ModelTrait, QueryFilter, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use crate::{errors::UpubError, model::{self, addressing::Event}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}};
|
use crate::{errors::UpubError, model::{self, addressing::Event}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}};
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ pub async fn view(
|
||||||
// .set_id(Some(&crate::url!(ctx, "/objects/{id}/replies")))
|
// .set_id(Some(&crate::url!(ctx, "/objects/{id}/replies")))
|
||||||
// .set_first(apb::Node::link(crate::url!(ctx, "/objects/{id}/replies/page")))
|
// .set_first(apb::Node::link(crate::url!(ctx, "/objects/{id}/replies/page")))
|
||||||
.set_collection_type(Some(apb::CollectionType::Collection))
|
.set_collection_type(Some(apb::CollectionType::Collection))
|
||||||
.set_total_items(Some(object.comments as u64))
|
.set_total_items(Some(object.replies as u64))
|
||||||
.set_items(apb::Node::links(replies_ids))
|
.set_items(apb::Node::links(replies_ids))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use axum::extract::{Path, Query, State};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect, SelectColumns};
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QuerySelect, SelectColumns};
|
||||||
|
|
||||||
use apb::{ActorMut, EndpointsMut, Node};
|
use apb::{ActorMut, EndpointsMut, Node};
|
||||||
use crate::{errors::UpubError, model::{self, user}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}, url};
|
use crate::{errors::UpubError, model, server::{auth::AuthIdentity, fetcher::Fetcher, Context}, url};
|
||||||
|
|
||||||
use super::{jsonld::LD, JsonLD, TryFetch};
|
use super::{jsonld::LD, JsonLD, TryFetch};
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ pub async fn view(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match user::Entity::find_by_id(&uid)
|
match model::actor::Entity::find_by_ap_id(&uid)
|
||||||
.find_also_related(model::config::Entity)
|
.find_also_related(model::config::Entity)
|
||||||
.one(ctx.db()).await?
|
.one(ctx.db()).await?
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,7 +35,7 @@ pub async fn nodeinfo_discovery(State(ctx): State<Context>) -> Json<NodeInfoDisc
|
||||||
pub async fn nodeinfo(State(ctx): State<Context>, Path(version): Path<String>) -> Result<Json<nodeinfo::NodeInfoOwned>, StatusCode> {
|
pub async fn nodeinfo(State(ctx): State<Context>, Path(version): Path<String>) -> Result<Json<nodeinfo::NodeInfoOwned>, StatusCode> {
|
||||||
// TODO it's unsustainable to count these every time, especially comments since it's a complex
|
// TODO it's unsustainable to count these every time, especially comments since it's a complex
|
||||||
// filter! keep these numbers caches somewhere, maybe db, so that we can just look them up
|
// filter! keep these numbers caches somewhere, maybe db, so that we can just look them up
|
||||||
let total_users = model::user::Entity::find().count(ctx.db()).await.ok();
|
let total_users = model::actor::Entity::find().count(ctx.db()).await.ok();
|
||||||
let total_posts = None;
|
let total_posts = None;
|
||||||
let total_comments = None;
|
let total_comments = None;
|
||||||
let (software, version) = match version.as_str() {
|
let (software, version) = match version.as_str() {
|
||||||
|
@ -103,28 +103,9 @@ pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<Webfinger
|
||||||
.split_once('@')
|
.split_once('@')
|
||||||
{
|
{
|
||||||
if domain == ctx.domain() {
|
if domain == ctx.domain() {
|
||||||
if user == ctx.domain() {
|
|
||||||
// we fetch with our domain as user, they are checking us back, this is a special edge case
|
|
||||||
Ok(JsonRD(JsonResourceDescriptor {
|
|
||||||
subject: format!("acct:{user}@{domain}"),
|
|
||||||
aliases: vec![ctx.base().to_string()],
|
|
||||||
links: vec![
|
|
||||||
JsonResourceDescriptorLink {
|
|
||||||
rel: "self".to_string(),
|
|
||||||
link_type: Some("application/ld+json".to_string()),
|
|
||||||
href: Some(ctx.base().to_string()),
|
|
||||||
properties: jrd::Map::default(),
|
|
||||||
titles: jrd::Map::default(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
expires: None,
|
|
||||||
properties: jrd::Map::default(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// local user
|
// local user
|
||||||
let uid = ctx.uid(user);
|
let uid = ctx.uid(user);
|
||||||
let usr = model::user::Entity::find_by_id(uid)
|
let usr = model::actor::Entity::find_by_ap_id(&uid)
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(UpubError::not_found)?;
|
.ok_or_else(UpubError::not_found)?;
|
||||||
|
@ -144,13 +125,11 @@ pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<Webfinger
|
||||||
expires: None,
|
expires: None,
|
||||||
properties: jrd::Map::default(),
|
properties: jrd::Map::default(),
|
||||||
}))
|
}))
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// remote user
|
// remote user
|
||||||
let usr = model::user::Entity::find()
|
let usr = model::actor::Entity::find()
|
||||||
.filter(model::user::Column::PreferredUsername.eq(user))
|
.filter(model::actor::Column::PreferredUsername.eq(user))
|
||||||
.filter(model::user::Column::Domain.eq(domain))
|
.filter(model::actor::Column::Domain.eq(domain))
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(UpubError::not_found)?;
|
.ok_or_else(UpubError::not_found)?;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use axum::{extract::{Path, State}, http::StatusCode, Json};
|
use axum::{extract::{Path, State}, http::StatusCode, Json};
|
||||||
use mastodon_async_entities::account::{Account, AccountId};
|
use mastodon_async_entities::account::{Account, AccountId};
|
||||||
use sea_orm::EntityTrait;
|
|
||||||
|
|
||||||
use crate::{model, server::{auth::AuthIdentity, Context}};
|
use crate::{model, server::{auth::AuthIdentity, Context}};
|
||||||
|
|
||||||
|
@ -9,7 +8,7 @@ pub async fn view(
|
||||||
AuthIdentity(_auth): AuthIdentity,
|
AuthIdentity(_auth): AuthIdentity,
|
||||||
Path(id): Path<String>
|
Path(id): Path<String>
|
||||||
) -> Result<Json<Account>, StatusCode> {
|
) -> Result<Json<Account>, StatusCode> {
|
||||||
match model::user::Entity::find_by_id(ctx.uid(&id))
|
match model::actor::Entity::find_by_ap_id(&ctx.uid(&id))
|
||||||
.find_also_related(model::config::Entity)
|
.find_also_related(model::config::Entity)
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use sea_orm::{EntityTrait, IntoActiveModel};
|
use sea_orm::{ActiveValue::{Set, NotSet}, EntityTrait};
|
||||||
|
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
pub trait Administrable {
|
pub trait Administrable {
|
||||||
|
@ -28,52 +28,56 @@ impl Administrable for super::Context {
|
||||||
let ap_id = self.uid(&username);
|
let ap_id = self.uid(&username);
|
||||||
let db = self.db();
|
let db = self.db();
|
||||||
let domain = self.domain().to_string();
|
let domain = self.domain().to_string();
|
||||||
let user_model = crate::model::user::Model {
|
let user_model = crate::model::actor::Model {
|
||||||
id: ap_id.clone(),
|
internal: NotSet,
|
||||||
name: display_name,
|
id: Set(ap_id.clone()),
|
||||||
domain, summary,
|
name: Set(display_name),
|
||||||
preferred_username: username.clone(),
|
domain: Set(domain),
|
||||||
following: None,
|
summary: Set(summary),
|
||||||
following_count: 0,
|
preferred_username: Set(username.clone()),
|
||||||
followers: None,
|
following: Set(None),
|
||||||
followers_count: 0,
|
following_count: Set(0),
|
||||||
statuses_count: 0,
|
followers: Set(None),
|
||||||
icon: avatar_url,
|
followers_count: Set(0),
|
||||||
image: banner_url,
|
statuses_count: Set(0),
|
||||||
inbox: None,
|
icon: Set(avatar_url),
|
||||||
shared_inbox: None,
|
image: Set(banner_url),
|
||||||
outbox: None,
|
inbox: Set(None),
|
||||||
actor_type: apb::ActorType::Person,
|
shared_inbox: Set(None),
|
||||||
created: chrono::Utc::now(),
|
outbox: Set(None),
|
||||||
updated: chrono::Utc::now(),
|
actor_type: Set(apb::ActorType::Person),
|
||||||
private_key: Some(std::str::from_utf8(&key.private_key_to_pem().unwrap()).unwrap().to_string()),
|
created: Set(chrono::Utc::now()),
|
||||||
public_key: std::str::from_utf8(&key.public_key_to_pem().unwrap()).unwrap().to_string(),
|
updated: Set(chrono::Utc::now()),
|
||||||
|
private_key: Set(Some(std::str::from_utf8(&key.private_key_to_pem().unwrap()).unwrap().to_string())),
|
||||||
|
public_key: Set(std::str::from_utf8(&key.public_key_to_pem().unwrap()).unwrap().to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
crate::model::user::Entity::insert(user_model.into_active_model())
|
crate::model::actor::Entity::insert(user_model)
|
||||||
.exec(db)
|
.exec(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let config_model = crate::model::config::Model {
|
let config_model = crate::model::config::ActiveModel {
|
||||||
id: ap_id.clone(),
|
internal: NotSet,
|
||||||
accept_follow_requests: true,
|
actor: Set(ap_id.clone()),
|
||||||
show_followers_count: true,
|
accept_follow_requests: Set(true),
|
||||||
show_following_count: true,
|
show_followers_count: Set(true),
|
||||||
show_followers: false,
|
show_following_count: Set(true),
|
||||||
show_following: false,
|
show_followers: Set(false),
|
||||||
|
show_following: Set(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
crate::model::config::Entity::insert(config_model.into_active_model())
|
crate::model::config::Entity::insert(config_model)
|
||||||
.exec(db)
|
.exec(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let credentials_model = crate::model::credential::Model {
|
let credentials_model = crate::model::credential::ActiveModel {
|
||||||
id: ap_id,
|
internal: NotSet,
|
||||||
email: username,
|
actor: Set(ap_id),
|
||||||
password,
|
login: Set(username),
|
||||||
|
password: Set(password),
|
||||||
};
|
};
|
||||||
|
|
||||||
crate::model::credential::Entity::insert(credentials_model.into_active_model())
|
crate::model::credential::Entity::insert(credentials_model)
|
||||||
.exec(db)
|
.exec(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ async fn worker(db: &DatabaseConnection, domain: &str, poll_interval: u64, waker
|
||||||
};
|
};
|
||||||
|
|
||||||
let del_row = model::delivery::ActiveModel {
|
let del_row = model::delivery::ActiveModel {
|
||||||
id: sea_orm::ActiveValue::Set(delivery.id),
|
internal: sea_orm::ActiveValue::Set(delivery.internal),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let del = model::delivery::Entity::delete(del_row)
|
let del = model::delivery::Entity::delete(del_row)
|
||||||
|
@ -72,7 +72,7 @@ async fn worker(db: &DatabaseConnection, domain: &str, poll_interval: u64, waker
|
||||||
|
|
||||||
tracing::info!("delivering {} to {}", delivery.activity, delivery.target);
|
tracing::info!("delivering {} to {}", delivery.activity, delivery.target);
|
||||||
|
|
||||||
let payload = match model::activity::Entity::find_by_id(&delivery.activity)
|
let payload = match model::activity::Entity::find_by_ap_id(&delivery.activity)
|
||||||
.find_also_related(model::object::Entity)
|
.find_also_related(model::object::Entity)
|
||||||
.one(db)
|
.one(db)
|
||||||
.await? // TODO probably should not fail here and at least re-insert the delivery
|
.await? // TODO probably should not fail here and at least re-insert the delivery
|
||||||
|
@ -99,24 +99,19 @@ async fn worker(db: &DatabaseConnection, domain: &str, poll_interval: u64, waker
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = if delivery.actor == format!("https://{domain}") {
|
let Some(actor) = model::actor::Entity::find_by_ap_id(&delivery.actor)
|
||||||
let Some(model::application::Model { private_key: key, .. }) = model::application::Entity::find()
|
.one(db)
|
||||||
.one(db).await?
|
.await?
|
||||||
else {
|
else {
|
||||||
tracing::error!("no private key configured for application");
|
tracing::error!("abandoning delivery of {} from non existant actor: {}", delivery.activity, delivery.actor);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
key
|
|
||||||
} else {
|
|
||||||
let Some(model::user::Model{ private_key: Some(key), .. }) = model::user::Entity::find_by_id(&delivery.actor)
|
|
||||||
.one(db).await?
|
|
||||||
else {
|
|
||||||
tracing::error!("can not dispatch activity for user without private key: {}", delivery.actor);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
key
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let Some(key) = actor.private_key
|
||||||
|
else {
|
||||||
|
tracing::error!("abandoning delivery of {} from actor without private key: {}", delivery.activity, delivery.actor);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = Context::request(
|
if let Err(e) = Context::request(
|
||||||
Method::POST, &delivery.target,
|
Method::POST, &delivery.target,
|
||||||
|
@ -125,7 +120,7 @@ async fn worker(db: &DatabaseConnection, domain: &str, poll_interval: u64, waker
|
||||||
).await {
|
).await {
|
||||||
tracing::warn!("failed delivery of {} to {} : {e}", delivery.activity, delivery.target);
|
tracing::warn!("failed delivery of {} to {} : {e}", delivery.activity, delivery.target);
|
||||||
let new_delivery = model::delivery::ActiveModel {
|
let new_delivery = model::delivery::ActiveModel {
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
not_before: sea_orm::ActiveValue::Set(delivery.next_delivery()),
|
not_before: sea_orm::ActiveValue::Set(delivery.next_delivery()),
|
||||||
actor: sea_orm::ActiveValue::Set(delivery.actor),
|
actor: sea_orm::ActiveValue::Set(delivery.actor),
|
||||||
target: sea_orm::ActiveValue::Set(delivery.target),
|
target: sea_orm::ActiveValue::Set(delivery.target),
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use apb::{target::Addressed, Activity, Base, Collection, CollectionPage, Link, Object};
|
use apb::{target::Addressed, Activity, Actor, ActorMut, Base, Collection, Link, Object};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use reqwest::{header::{ACCEPT, CONTENT_TYPE, USER_AGENT}, Method, Response};
|
use reqwest::{header::{ACCEPT, CONTENT_TYPE, USER_AGENT}, Method, Response};
|
||||||
use sea_orm::{sea_query::Expr, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
|
use sea_orm::EntityTrait;
|
||||||
|
|
||||||
use crate::{errors::UpubError, model, VERSION};
|
use crate::{errors::UpubError, model, VERSION};
|
||||||
|
|
||||||
|
@ -13,14 +13,14 @@ use super::{httpsign::HttpSignature, normalizer::Normalizer, Context};
|
||||||
pub trait Fetcher {
|
pub trait Fetcher {
|
||||||
async fn webfinger(&self, user: &str, host: &str) -> crate::Result<String>;
|
async fn webfinger(&self, user: &str, host: &str) -> crate::Result<String>;
|
||||||
|
|
||||||
async fn fetch_user(&self, id: &str) -> crate::Result<model::user::Model>;
|
async fn fetch_user(&self, id: &str) -> crate::Result<model::actor::Model>;
|
||||||
async fn pull_user(&self, id: &str) -> crate::Result<model::user::Model>;
|
async fn pull_user(&self, id: &str) -> crate::Result<serde_json::Value>;
|
||||||
|
|
||||||
async fn fetch_object(&self, id: &str) -> crate::Result<model::object::Model>;
|
async fn fetch_object(&self, id: &str) -> crate::Result<model::object::Model>;
|
||||||
async fn pull_object(&self, id: &str) -> crate::Result<model::object::Model>;
|
async fn pull_object(&self, id: &str) -> crate::Result<serde_json::Value>;
|
||||||
|
|
||||||
async fn fetch_activity(&self, id: &str) -> crate::Result<model::activity::Model>;
|
async fn fetch_activity(&self, id: &str) -> crate::Result<model::activity::Model>;
|
||||||
async fn pull_activity(&self, id: &str) -> crate::Result<model::activity::Model>;
|
async fn pull_activity(&self, id: &str) -> crate::Result<serde_json::Value>;
|
||||||
|
|
||||||
async fn fetch_thread(&self, id: &str) -> crate::Result<()>;
|
async fn fetch_thread(&self, id: &str) -> crate::Result<()>;
|
||||||
|
|
||||||
|
@ -115,30 +115,35 @@ impl Fetcher for Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn fetch_user(&self, id: &str) -> crate::Result<model::user::Model> {
|
async fn fetch_user(&self, id: &str) -> crate::Result<model::actor::Model> {
|
||||||
if let Some(x) = model::user::Entity::find_by_id(id).one(self.db()).await? {
|
if let Some(x) = model::actor::Entity::find_by_ap_id(id).one(self.db()).await? {
|
||||||
return Ok(x); // already in db, easy
|
return Ok(x); // already in db, easy
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_model = self.pull_user(id).await?;
|
let user_document = self.pull_user(id).await?;
|
||||||
|
let user_model = model::actor::ActiveModel::new(&user_document)?;
|
||||||
|
|
||||||
// TODO this may fail: while fetching, remote server may fetch our service actor.
|
// TODO this may fail: while fetching, remote server may fetch our service actor.
|
||||||
// if it does so with http signature, we will fetch that actor in background
|
// if it does so with http signature, we will fetch that actor in background
|
||||||
// meaning that, once we reach here, it's already inserted and returns an UNIQUE error
|
// meaning that, once we reach here, it's already inserted and returns an UNIQUE error
|
||||||
model::user::Entity::insert(user_model.clone().into_active_model())
|
model::actor::Entity::insert(user_model).exec(self.db()).await?;
|
||||||
.exec(self.db()).await?;
|
|
||||||
|
|
||||||
Ok(user_model)
|
// TODO fetch it back to get the internal id
|
||||||
|
Ok(
|
||||||
|
model::actor::Entity::find_by_ap_id(id)
|
||||||
|
.one(self.db())
|
||||||
|
.await?
|
||||||
|
.ok_or_else(UpubError::internal_server_error)?
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn pull_user(&self, id: &str) -> crate::Result<model::user::Model> {
|
async fn pull_user(&self, id: &str) -> crate::Result<serde_json::Value> {
|
||||||
let user = Self::request(
|
let mut user = Self::request(
|
||||||
Method::GET, id, None, &format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
Method::GET, id, None, &format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
||||||
).await?.json::<serde_json::Value>().await?;
|
).await?.json::<serde_json::Value>().await?;
|
||||||
let mut user_model = model::user::Model::new(&user)?;
|
|
||||||
|
|
||||||
// TODO try fetching these numbers from audience/generator fields to avoid making 2 more GETs
|
// TODO try fetching these numbers from audience/generator fields to avoid making 2 more GETs
|
||||||
if let Some(followers_url) = &user_model.followers {
|
if let Some(followers_url) = &user.followers().id() {
|
||||||
let req = Self::request(
|
let req = Self::request(
|
||||||
Method::GET, followers_url, None,
|
Method::GET, followers_url, None,
|
||||||
&format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
&format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
||||||
|
@ -146,13 +151,13 @@ impl Fetcher for Context {
|
||||||
if let Ok(res) = req {
|
if let Ok(res) = req {
|
||||||
if let Ok(user_followers) = res.json::<serde_json::Value>().await {
|
if let Ok(user_followers) = res.json::<serde_json::Value>().await {
|
||||||
if let Some(total) = user_followers.total_items() {
|
if let Some(total) = user_followers.total_items() {
|
||||||
user_model.followers_count = total as i64;
|
user = user.set_followers_count(Some(total));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(following_url) = &user_model.following {
|
if let Some(following_url) = &user.following().id() {
|
||||||
let req = Self::request(
|
let req = Self::request(
|
||||||
Method::GET, following_url, None,
|
Method::GET, following_url, None,
|
||||||
&format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
&format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
||||||
|
@ -160,33 +165,38 @@ impl Fetcher for Context {
|
||||||
if let Ok(res) = req {
|
if let Ok(res) = req {
|
||||||
if let Ok(user_following) = res.json::<serde_json::Value>().await {
|
if let Ok(user_following) = res.json::<serde_json::Value>().await {
|
||||||
if let Some(total) = user_following.total_items() {
|
if let Some(total) = user_following.total_items() {
|
||||||
user_model.following_count = total as i64;
|
user = user.set_following_count(Some(total));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(user_model)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_activity(&self, id: &str) -> crate::Result<model::activity::Model> {
|
async fn fetch_activity(&self, id: &str) -> crate::Result<model::activity::Model> {
|
||||||
if let Some(x) = model::activity::Entity::find_by_id(id).one(self.db()).await? {
|
if let Some(x) = model::activity::Entity::find_by_ap_id(id).one(self.db()).await? {
|
||||||
return Ok(x); // already in db, easy
|
return Ok(x); // already in db, easy
|
||||||
}
|
}
|
||||||
|
|
||||||
let activity_model = self.pull_activity(id).await?;
|
let activity_document = self.pull_activity(id).await?;
|
||||||
|
let activity_model = model::activity::ActiveModel::new(&activity_document)?;
|
||||||
|
|
||||||
model::activity::Entity::insert(activity_model.clone().into_active_model())
|
model::activity::Entity::insert(activity_model)
|
||||||
.exec(self.db()).await?;
|
.exec(self.db()).await?;
|
||||||
|
|
||||||
let addressed = activity_model.addressed();
|
// TODO fetch it back to get the internal id
|
||||||
let expanded_addresses = self.expand_addressing(addressed).await?;
|
let activity = model::activity::Entity::find_by_ap_id(id)
|
||||||
self.address_to(Some(&activity_model.id), None, &expanded_addresses).await?;
|
.one(self.db()).await?.ok_or_else(UpubError::internal_server_error)?;
|
||||||
|
|
||||||
Ok(activity_model)
|
let addressed = activity.addressed();
|
||||||
|
let expanded_addresses = self.expand_addressing(addressed).await?;
|
||||||
|
self.address_to(Some(&activity.id), None, &expanded_addresses).await?;
|
||||||
|
|
||||||
|
Ok(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn pull_activity(&self, id: &str) -> crate::Result<model::activity::Model> {
|
async fn pull_activity(&self, id: &str) -> crate::Result<serde_json::Value> {
|
||||||
let activity = Self::request(
|
let activity = Self::request(
|
||||||
Method::GET, id, None, &format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
Method::GET, id, None, &format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
||||||
).await?.json::<serde_json::Value>().await?;
|
).await?.json::<serde_json::Value>().await?;
|
||||||
|
@ -203,84 +213,33 @@ impl Fetcher for Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let activity_model = model::activity::Model::new(&activity)?;
|
Ok(activity)
|
||||||
|
|
||||||
Ok(activity_model)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_thread(&self, id: &str) -> crate::Result<()> {
|
async fn fetch_thread(&self, id: &str) -> crate::Result<()> {
|
||||||
crawl_replies(self, id, 0).await
|
// crawl_replies(self, id, 0).await
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_object(&self, id: &str) -> crate::Result<model::object::Model> {
|
async fn fetch_object(&self, id: &str) -> crate::Result<model::object::Model> {
|
||||||
fetch_object_inner(self, id, 0).await
|
fetch_object_inner(self, id, 0).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn pull_object(&self, id: &str) -> crate::Result<model::object::Model> {
|
async fn pull_object(&self, id: &str) -> crate::Result<serde_json::Value> {
|
||||||
let object = Context::request(
|
Ok(
|
||||||
|
Context::request(
|
||||||
Method::GET, id, None, &format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
Method::GET, id, None, &format!("https://{}", self.domain()), &self.app().private_key, self.domain(),
|
||||||
).await?.json::<serde_json::Value>().await?;
|
)
|
||||||
|
.await?
|
||||||
Ok(model::object::Model::new(&object)?)
|
.json::<serde_json::Value>()
|
||||||
|
.await?
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
|
||||||
async fn crawl_replies(ctx: &Context, id: &str, depth: usize) -> crate::Result<()> {
|
|
||||||
tracing::info!("crawling replies of '{id}'");
|
|
||||||
let object = Context::request(
|
|
||||||
Method::GET, id, None, &format!("https://{}", ctx.domain()), &ctx.app().private_key, ctx.domain(),
|
|
||||||
).await?.json::<serde_json::Value>().await?;
|
|
||||||
|
|
||||||
let object_model = model::object::Model::new(&object)?;
|
|
||||||
match model::object::Entity::insert(object_model.into_active_model())
|
|
||||||
.exec(ctx.db()).await
|
|
||||||
{
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(sea_orm::DbErr::RecordNotInserted) => {},
|
|
||||||
Err(sea_orm::DbErr::Exec(_)) => {}, // ughhh bad fix for sqlite
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
}
|
|
||||||
|
|
||||||
if depth > 16 {
|
|
||||||
tracing::warn!("stopping thread crawling: too deep!");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut page_url = match object.replies().get() {
|
|
||||||
Some(serde_json::Value::String(x)) => {
|
|
||||||
let replies = Context::request(
|
|
||||||
Method::GET, x, None, &format!("https://{}", ctx.domain()), &ctx.app().private_key, ctx.domain(),
|
|
||||||
).await?.json::<serde_json::Value>().await?;
|
|
||||||
replies.first().id()
|
|
||||||
},
|
|
||||||
Some(serde_json::Value::Object(x)) => {
|
|
||||||
let obj = serde_json::Value::Object(x.clone()); // lol putting it back, TODO!
|
|
||||||
obj.first().id()
|
|
||||||
},
|
|
||||||
_ => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
while let Some(ref url) = page_url {
|
|
||||||
let replies = Context::request(
|
|
||||||
Method::GET, url, None, &format!("https://{}", ctx.domain()), &ctx.app().private_key, ctx.domain(),
|
|
||||||
).await?.json::<serde_json::Value>().await?;
|
|
||||||
|
|
||||||
for reply in replies.items() {
|
|
||||||
// TODO right now it crawls one by one, could be made in parallel but would be quite more
|
|
||||||
// abusive, so i'll keep it like this while i try it out
|
|
||||||
crawl_replies(ctx, reply.href(), depth + 1).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
page_url = replies.next().id();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
#[async_recursion::async_recursion]
|
||||||
async fn fetch_object_inner(ctx: &Context, id: &str, depth: usize) -> crate::Result<model::object::Model> {
|
async fn fetch_object_inner(ctx: &Context, id: &str, depth: usize) -> crate::Result<model::object::Model> {
|
||||||
if let Some(x) = model::object::Entity::find_by_id(id).one(ctx.db()).await? {
|
if let Some(x) = model::object::Entity::find_by_ap_id(id).one(ctx.db()).await? {
|
||||||
return Ok(x); // already in db, easy
|
return Ok(x); // already in db, easy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +249,7 @@ async fn fetch_object_inner(ctx: &Context, id: &str, depth: usize) -> crate::Res
|
||||||
|
|
||||||
if let Some(oid) = object.id() {
|
if let Some(oid) = object.id() {
|
||||||
if oid != id {
|
if oid != id {
|
||||||
if let Some(x) = model::object::Entity::find_by_id(oid).one(ctx.db()).await? {
|
if let Some(x) = model::object::Entity::find_by_ap_id(oid).one(ctx.db()).await? {
|
||||||
return Ok(x); // already in db, but with id different that given url
|
return Ok(x); // already in db, but with id different that given url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,3 +300,56 @@ impl Fetchable for apb::Node<serde_json::Value> {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[async_recursion::async_recursion]
|
||||||
|
// async fn crawl_replies(ctx: &Context, id: &str, depth: usize) -> crate::Result<()> {
|
||||||
|
// tracing::info!("crawling replies of '{id}'");
|
||||||
|
// let object = Context::request(
|
||||||
|
// Method::GET, id, None, &format!("https://{}", ctx.domain()), &ctx.app().private_key, ctx.domain(),
|
||||||
|
// ).await?.json::<serde_json::Value>().await?;
|
||||||
|
//
|
||||||
|
// let object_model = model::object::Model::new(&object)?;
|
||||||
|
// match model::object::Entity::insert(object_model.into_active_model())
|
||||||
|
// .exec(ctx.db()).await
|
||||||
|
// {
|
||||||
|
// Ok(_) => {},
|
||||||
|
// Err(sea_orm::DbErr::RecordNotInserted) => {},
|
||||||
|
// Err(sea_orm::DbErr::Exec(_)) => {}, // ughhh bad fix for sqlite
|
||||||
|
// Err(e) => return Err(e.into()),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if depth > 16 {
|
||||||
|
// tracing::warn!("stopping thread crawling: too deep!");
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// let mut page_url = match object.replies().get() {
|
||||||
|
// Some(serde_json::Value::String(x)) => {
|
||||||
|
// let replies = Context::request(
|
||||||
|
// Method::GET, x, None, &format!("https://{}", ctx.domain()), &ctx.app().private_key, ctx.domain(),
|
||||||
|
// ).await?.json::<serde_json::Value>().await?;
|
||||||
|
// replies.first().id()
|
||||||
|
// },
|
||||||
|
// Some(serde_json::Value::Object(x)) => {
|
||||||
|
// let obj = serde_json::Value::Object(x.clone()); // lol putting it back, TODO!
|
||||||
|
// obj.first().id()
|
||||||
|
// },
|
||||||
|
// _ => return Ok(()),
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// while let Some(ref url) = page_url {
|
||||||
|
// let replies = Context::request(
|
||||||
|
// Method::GET, url, None, &format!("https://{}", ctx.domain()), &ctx.app().private_key, ctx.domain(),
|
||||||
|
// ).await?.json::<serde_json::Value>().await?;
|
||||||
|
//
|
||||||
|
// for reply in replies.items() {
|
||||||
|
// // TODO right now it crawls one by one, could be made in parallel but would be quite more
|
||||||
|
// // abusive, so i'll keep it like this while i try it out
|
||||||
|
// crawl_replies(ctx, reply.href(), depth + 1).await?;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// page_url = replies.next().id();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use apb::{Node, Base, Object, Document};
|
use apb::{Node, Base, Object, Document};
|
||||||
use sea_orm::{sea_query::Expr, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set};
|
use sea_orm::{sea_query::Expr, ActiveValue::Set, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
|
||||||
use crate::{errors::UpubError, model, server::Context};
|
use crate::{errors::UpubError, model, server::Context};
|
||||||
|
|
||||||
use super::fetcher::Fetcher;
|
use super::fetcher::Fetcher;
|
||||||
|
@ -12,23 +12,23 @@ pub trait Normalizer {
|
||||||
#[axum::async_trait]
|
#[axum::async_trait]
|
||||||
impl Normalizer for super::Context {
|
impl Normalizer for super::Context {
|
||||||
async fn insert_object(&self, object_node: impl apb::Object, server: Option<String>) -> crate::Result<model::object::Model> {
|
async fn insert_object(&self, object_node: impl apb::Object, server: Option<String>) -> crate::Result<model::object::Model> {
|
||||||
let mut object_model = model::object::Model::new(&object_node)?;
|
let oid = object_node.id().ok_or_else(UpubError::bad_request)?.to_string();
|
||||||
let oid = object_model.id.clone();
|
let uid = object_node.attributed_to().id();
|
||||||
let uid = object_model.attributed_to.clone();
|
let mut object_model = model::object::ActiveModel::new(&object_node)?;
|
||||||
if let Some(server) = server {
|
if let Some(server) = server {
|
||||||
// make sure we're allowed to create this object
|
// make sure we're allowed to create this object
|
||||||
if let Some(object_author) = &object_model.attributed_to {
|
if let Set(Some(object_author)) = &object_model.attributed_to {
|
||||||
if server != Context::server(object_author) {
|
if server != Context::server(object_author) {
|
||||||
return Err(UpubError::forbidden());
|
return Err(UpubError::forbidden());
|
||||||
}
|
}
|
||||||
} else if server != Context::server(&object_model.id) {
|
} else if server != Context::server(&oid) {
|
||||||
return Err(UpubError::forbidden());
|
return Err(UpubError::forbidden());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure content only contains a safe subset of html
|
// make sure content only contains a safe subset of html
|
||||||
if let Some(content) = object_model.content {
|
if let Set(Some(content)) = object_model.content {
|
||||||
object_model.content = Some(mdhtml::safe_html(&content));
|
object_model.content = Set(Some(mdhtml::safe_html(&content)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix context for remote posts
|
// fix context for remote posts
|
||||||
|
@ -37,33 +37,34 @@ impl Normalizer for super::Context {
|
||||||
// > btw! also if any link is broken or we get rate limited, the whole insertion fails which is
|
// > btw! also if any link is broken or we get rate limited, the whole insertion fails which is
|
||||||
// > kind of dumb. there should be a job system so this can be done in waves. or maybe there's
|
// > kind of dumb. there should be a job system so this can be done in waves. or maybe there's
|
||||||
// > some whole other way to do this?? im thinking but misskey aaaa!! TODO
|
// > some whole other way to do this?? im thinking but misskey aaaa!! TODO
|
||||||
if let Some(ref reply) = object_model.in_reply_to {
|
if let Set(Some(ref reply)) = object_model.in_reply_to {
|
||||||
if let Some(o) = model::object::Entity::find_by_id(reply).one(self.db()).await? {
|
if let Some(o) = model::object::Entity::find_by_ap_id(reply).one(self.db()).await? {
|
||||||
object_model.context = o.context;
|
object_model.context = Set(o.context);
|
||||||
} else {
|
} else {
|
||||||
object_model.context = None; // TODO to be filled by some other task
|
object_model.context = Set(None); // TODO to be filled by some other task
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
object_model.context = Some(object_model.id.clone());
|
object_model.context = Set(Some(oid.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
model::object::Entity::insert(object_model.clone().into_active_model()).exec(self.db()).await?;
|
model::object::Entity::insert(object_model.clone().into_active_model()).exec(self.db()).await?;
|
||||||
|
let object = model::object::Entity::find_by_ap_id(&oid).one(self.db()).await?.ok_or_else(UpubError::internal_server_error)?;
|
||||||
|
|
||||||
// update replies counter
|
// update replies counter
|
||||||
if let Some(ref in_reply_to) = object_model.in_reply_to {
|
if let Set(Some(ref in_reply_to)) = object_model.in_reply_to {
|
||||||
if self.fetch_object(in_reply_to).await.is_ok() {
|
if self.fetch_object(in_reply_to).await.is_ok() {
|
||||||
model::object::Entity::update_many()
|
model::object::Entity::update_many()
|
||||||
.filter(model::object::Column::Id.eq(in_reply_to))
|
.filter(model::object::Column::Id.eq(in_reply_to))
|
||||||
.col_expr(model::object::Column::Comments, Expr::col(model::object::Column::Comments).add(1))
|
.col_expr(model::object::Column::Replies, Expr::col(model::object::Column::Replies).add(1))
|
||||||
.exec(self.db())
|
.exec(self.db())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update statuses counter
|
// update statuses counter
|
||||||
if let Some(object_author) = uid {
|
if let Some(object_author) = uid {
|
||||||
model::user::Entity::update_many()
|
model::actor::Entity::update_many()
|
||||||
.col_expr(model::user::Column::StatusesCount, Expr::col(model::user::Column::StatusesCount).add(1))
|
.col_expr(model::actor::Column::StatusesCount, Expr::col(model::actor::Column::StatusesCount).add(1))
|
||||||
.filter(model::user::Column::Id.eq(&object_author))
|
.filter(model::actor::Column::Id.eq(&object_author))
|
||||||
.exec(self.db())
|
.exec(self.db())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@ -76,18 +77,18 @@ impl Normalizer for super::Context {
|
||||||
continue
|
continue
|
||||||
},
|
},
|
||||||
Node::Link(l) => model::attachment::ActiveModel {
|
Node::Link(l) => model::attachment::ActiveModel {
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
url: Set(l.href().to_string()),
|
url: Set(l.href().to_string()),
|
||||||
object: Set(oid.clone()),
|
object: Set(object.internal),
|
||||||
document_type: Set(apb::DocumentType::Page),
|
document_type: Set(apb::DocumentType::Page),
|
||||||
name: Set(l.link_name().map(|x| x.to_string())),
|
name: Set(l.link_name().map(|x| x.to_string())),
|
||||||
media_type: Set(l.link_media_type().unwrap_or("link").to_string()),
|
media_type: Set(l.link_media_type().unwrap_or("link").to_string()),
|
||||||
created: Set(chrono::Utc::now()),
|
created: Set(chrono::Utc::now()),
|
||||||
},
|
},
|
||||||
Node::Object(o) => model::attachment::ActiveModel {
|
Node::Object(o) => model::attachment::ActiveModel {
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
url: Set(o.url().id().unwrap_or_else(|| o.id().map(|x| x.to_string()).unwrap_or_default())),
|
url: Set(o.url().id().unwrap_or_else(|| o.id().map(|x| x.to_string()).unwrap_or_default())),
|
||||||
object: Set(oid.clone()),
|
object: Set(object.internal),
|
||||||
document_type: Set(o.as_document().map_or(apb::DocumentType::Document, |x| x.document_type().unwrap_or(apb::DocumentType::Page))),
|
document_type: Set(o.as_document().map_or(apb::DocumentType::Document, |x| x.document_type().unwrap_or(apb::DocumentType::Page))),
|
||||||
name: Set(o.name().map(|x| x.to_string())),
|
name: Set(o.name().map(|x| x.to_string())),
|
||||||
media_type: Set(o.media_type().unwrap_or("link").to_string()),
|
media_type: Set(o.media_type().unwrap_or("link").to_string()),
|
||||||
|
@ -113,9 +114,9 @@ impl Normalizer for super::Context {
|
||||||
};
|
};
|
||||||
|
|
||||||
let attachment_model = model::attachment::ActiveModel {
|
let attachment_model = model::attachment::ActiveModel {
|
||||||
id: sea_orm::ActiveValue::NotSet,
|
internal: sea_orm::ActiveValue::NotSet,
|
||||||
url: Set(img.url().id().unwrap_or_else(|| img.id().map(|x| x.to_string()).unwrap_or_default())),
|
url: Set(img.url().id().unwrap_or_else(|| img.id().map(|x| x.to_string()).unwrap_or_default())),
|
||||||
object: Set(oid.clone()),
|
object: Set(object.internal),
|
||||||
document_type: Set(img.as_document().map_or(apb::DocumentType::Document, |x| x.document_type().unwrap_or(apb::DocumentType::Page))),
|
document_type: Set(img.as_document().map_or(apb::DocumentType::Document, |x| x.document_type().unwrap_or(apb::DocumentType::Page))),
|
||||||
name: Set(img.name().map(|x| x.to_string())),
|
name: Set(img.name().map(|x| x.to_string())),
|
||||||
media_type: Set(img.media_type().unwrap_or(media_type.as_deref().unwrap_or("link")).to_string()),
|
media_type: Set(img.media_type().unwrap_or(media_type.as_deref().unwrap_or("link")).to_string()),
|
||||||
|
@ -126,6 +127,6 @@ impl Normalizer for super::Context {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(object_model)
|
Ok(object)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue