2025-04-11 12:29:29 +01:00
|
|
|
use chrono::{DateTime, Local};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use sqlx::Sqlite;
|
2025-04-10 19:40:50 +01:00
|
|
|
|
2025-04-11 15:47:22 +01:00
|
|
|
use std::fmt::Debug;
|
2025-04-11 12:29:29 +01:00
|
|
|
pub mod http;
|
2025-04-10 19:40:50 +01:00
|
|
|
|
2025-04-11 12:29:29 +01:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct Actor {
|
|
|
|
id: String,
|
|
|
|
inbox: String,
|
|
|
|
outbox: String,
|
2025-04-10 19:40:50 +01:00
|
|
|
}
|
|
|
|
|
2025-04-11 15:47:22 +01:00
|
|
|
impl Actor {
|
|
|
|
pub fn from_raw(id: String, inbox: String, outbox: String) -> Self {
|
|
|
|
Self { id, inbox, outbox }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-11 12:29:29 +01:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct User {
|
|
|
|
id: String,
|
|
|
|
username: String,
|
|
|
|
actor: Actor,
|
|
|
|
display_name: String,
|
2025-04-10 19:40:50 +01:00
|
|
|
}
|
|
|
|
|
2025-04-11 12:29:29 +01:00
|
|
|
impl User {
|
|
|
|
pub fn id(&self) -> &str {
|
|
|
|
&self.id
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn username(&self) -> &str {
|
|
|
|
&self.username
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn actor_id(&self) -> &str {
|
|
|
|
&self.actor.id
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn display_name(&self) -> &str {
|
|
|
|
&self.display_name
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn actor(&self) -> &Actor {
|
|
|
|
&self.actor
|
|
|
|
}
|
2025-04-10 19:40:50 +01:00
|
|
|
|
2025-04-11 12:29:29 +01:00
|
|
|
pub async fn from_username(
|
|
|
|
username: &str,
|
|
|
|
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
|
|
|
) -> User {
|
|
|
|
let user = sqlx::query!(
|
|
|
|
r#"
|
|
|
|
SELECT u.*, a.id as "actor_own_id", a.inbox, a.outbox
|
|
|
|
FROM user u
|
|
|
|
INNER JOIN actor a ON u.actor_id = a.id
|
|
|
|
WHERE username = ?1
|
|
|
|
"#,
|
|
|
|
username
|
|
|
|
)
|
|
|
|
.fetch_one(conn)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
User {
|
|
|
|
id: user.id,
|
|
|
|
username: user.username,
|
|
|
|
actor: Actor {
|
|
|
|
id: user.actor_own_id,
|
|
|
|
inbox: user.inbox,
|
|
|
|
outbox: user.outbox,
|
|
|
|
},
|
|
|
|
display_name: user.display_name,
|
|
|
|
}
|
|
|
|
}
|
2025-04-10 19:40:50 +01:00
|
|
|
|
2025-04-11 12:29:29 +01:00
|
|
|
pub async fn from_actor_id(
|
|
|
|
actor_id: &str,
|
|
|
|
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
|
|
|
) -> User {
|
|
|
|
let user = sqlx::query!(
|
|
|
|
r#"
|
|
|
|
SELECT u.*, a.id as "actor_own_id", a.inbox, a.outbox
|
|
|
|
FROM user u
|
|
|
|
INNER JOIN actor a ON u.actor_id = a.id
|
|
|
|
WHERE actor_id = ?1
|
|
|
|
"#,
|
|
|
|
actor_id
|
|
|
|
)
|
|
|
|
.fetch_one(conn)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
User {
|
|
|
|
id: user.id,
|
|
|
|
username: user.username,
|
|
|
|
actor: Actor {
|
|
|
|
id: user.actor_own_id,
|
|
|
|
inbox: user.inbox,
|
|
|
|
outbox: user.outbox,
|
|
|
|
},
|
|
|
|
display_name: user.display_name,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum ActivityType {
|
|
|
|
Follow,
|
2025-04-11 15:47:22 +01:00
|
|
|
Accept,
|
|
|
|
Create,
|
|
|
|
Unknown
|
2025-04-11 12:29:29 +01:00
|
|
|
}
|
2025-04-10 19:40:50 +01:00
|
|
|
|
2025-04-11 12:29:29 +01:00
|
|
|
impl ActivityType {
|
|
|
|
fn to_raw(self) -> String {
|
|
|
|
match self {
|
|
|
|
ActivityType::Follow => "Follow".to_string(),
|
2025-04-11 15:47:22 +01:00
|
|
|
ActivityType::Accept => "Accept".to_string(),
|
|
|
|
ActivityType::Create => "Create".to_string(),
|
|
|
|
ActivityType::Unknown => "FIXME".to_string()
|
2025-04-11 12:29:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2025-04-11 15:47:22 +01:00
|
|
|
pub struct Activity<T : Serialize + Debug> {
|
2025-04-11 12:29:29 +01:00
|
|
|
pub id: String,
|
|
|
|
pub ty: ActivityType,
|
2025-04-11 15:47:22 +01:00
|
|
|
pub object: T,
|
2025-04-11 12:29:29 +01:00
|
|
|
pub published: DateTime<Local>,
|
2025-04-11 15:47:22 +01:00
|
|
|
pub to: Vec<String>,
|
|
|
|
pub cc: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T : Serialize + Debug + Default> Default for Activity<T> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
id: Default::default(),
|
|
|
|
ty: ActivityType::Unknown,
|
|
|
|
object: Default::default(),
|
|
|
|
published: Local::now(),
|
|
|
|
to: Default::default(),
|
|
|
|
cc: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
2025-04-11 12:29:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub type KeyId = String;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2025-04-11 15:47:22 +01:00
|
|
|
pub struct OutgoingActivity<T : Serialize + Debug> {
|
2025-04-11 12:29:29 +01:00
|
|
|
pub signed_by: KeyId,
|
2025-04-11 15:47:22 +01:00
|
|
|
pub req: Activity<T>,
|
2025-04-11 12:29:29 +01:00
|
|
|
pub to: Actor,
|
|
|
|
}
|
|
|
|
|
2025-04-11 15:47:22 +01:00
|
|
|
impl <T : Serialize + Debug> OutgoingActivity<T> {
|
|
|
|
pub async fn save(&self, conn: impl sqlx::Executor<'_, Database = Sqlite>) {
|
|
|
|
let ty = self.req.ty.clone().to_raw();
|
|
|
|
sqlx::query!(
|
|
|
|
r#"
|
|
|
|
INSERT INTO activity (id, ty, actor_id)
|
|
|
|
VALUES (?1, ?2, ?3)
|
|
|
|
"#,
|
|
|
|
self.req.id,
|
|
|
|
ty,
|
|
|
|
self.to.id
|
|
|
|
)
|
|
|
|
.execute(conn)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-11 12:29:29 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
2025-04-11 15:47:22 +01:00
|
|
|
struct RawActivity<T : Serialize + Debug> {
|
2025-04-11 12:29:29 +01:00
|
|
|
#[serde(rename = "@context")]
|
|
|
|
#[serde(skip_deserializing)]
|
|
|
|
context: String,
|
|
|
|
|
|
|
|
id: String,
|
|
|
|
#[serde(rename = "type")]
|
|
|
|
ty: String,
|
|
|
|
|
|
|
|
actor: String,
|
2025-04-11 15:47:22 +01:00
|
|
|
object: T,
|
2025-04-11 12:29:29 +01:00
|
|
|
published: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
type OutboxTransport = http::HttpClient;
|
|
|
|
pub struct Outbox<'a> {
|
|
|
|
user: User,
|
|
|
|
transport: &'a OutboxTransport,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Outbox<'a> {
|
|
|
|
pub fn user(&self) -> &User {
|
|
|
|
&self.user
|
|
|
|
}
|
|
|
|
|
2025-04-11 15:47:22 +01:00
|
|
|
pub async fn post<T : Serialize + Debug>(&self, activity: OutgoingActivity<T>) {
|
2025-04-11 12:29:29 +01:00
|
|
|
dbg!(&activity);
|
|
|
|
let raw = RawActivity {
|
|
|
|
context: "https://www.w3.org/ns/activitystreams".to_string(),
|
|
|
|
id: activity.req.id,
|
|
|
|
ty: activity.req.ty.to_raw(),
|
|
|
|
actor: self.user.actor.id.clone(),
|
|
|
|
object: activity.req.object,
|
|
|
|
published: activity.req.published.to_rfc3339(),
|
|
|
|
};
|
|
|
|
|
|
|
|
dbg!(&raw);
|
|
|
|
|
|
|
|
let follow_res = self
|
|
|
|
.transport
|
|
|
|
.post(activity.to.inbox)
|
|
|
|
.activity()
|
|
|
|
.json(&raw)
|
|
|
|
.sign(&activity.signed_by)
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.text()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
dbg!(follow_res);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn for_user(user: User, transport: &'a OutboxTransport) -> Outbox<'a> {
|
|
|
|
Outbox { user, transport }
|
|
|
|
}
|
2025-04-10 19:40:50 +01:00
|
|
|
}
|