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 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 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-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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2025-04-10 19:40:50 +01:00
|
|
|
pub struct Activity {
|
2025-04-11 12:29:29 +01:00
|
|
|
pub id: String,
|
|
|
|
pub ty: ActivityType,
|
|
|
|
pub object: String,
|
|
|
|
pub published: DateTime<Local>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type KeyId = String;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct OutgoingActivity {
|
|
|
|
pub signed_by: KeyId,
|
|
|
|
pub req: Activity,
|
|
|
|
pub to: Actor,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct RawActivity {
|
|
|
|
#[serde(rename = "@context")]
|
|
|
|
#[serde(skip_deserializing)]
|
|
|
|
context: String,
|
|
|
|
|
|
|
|
id: String,
|
|
|
|
#[serde(rename = "type")]
|
|
|
|
ty: String,
|
|
|
|
|
|
|
|
actor: String,
|
|
|
|
object: String,
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn post(&self, activity: OutgoingActivity) {
|
|
|
|
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
|
|
|
}
|