mirror of
https://github.com/nullishamy/ferri.git
synced 2025-04-29 20:29:23 +00:00
feat: first pass at mastoapi stuff
This commit is contained in:
parent
ce3a9bfb26
commit
244cb8b7e6
13 changed files with 368 additions and 83 deletions
|
@ -47,6 +47,34 @@ impl User {
|
|||
&self.actor
|
||||
}
|
||||
|
||||
pub async fn from_id(
|
||||
uuid: &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 u.id = ?1
|
||||
"#,
|
||||
uuid
|
||||
)
|
||||
.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,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_username(
|
||||
username: &str,
|
||||
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
||||
|
|
|
@ -4,12 +4,15 @@ use rocket::{
|
|||
FromForm, State,
|
||||
form::Form,
|
||||
post,
|
||||
serde::{Deserialize, Serialize},
|
||||
serde::{Deserialize, Serialize, json::Json},
|
||||
};
|
||||
use rocket_db_pools::Connection;
|
||||
use uuid::Uuid;
|
||||
use crate::timeline::TimelineStatus;
|
||||
|
||||
use crate::{AuthenticatedUser, Db, types::content};
|
||||
use crate::api::user::CredentialAcount;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromForm)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct Status {
|
||||
|
@ -26,7 +29,7 @@ pub async fn new_status(
|
|||
let user = ap::User::from_actor_id(&user.actor_id, &mut **db).await;
|
||||
let outbox = ap::Outbox::for_user(user.clone(), http);
|
||||
|
||||
let post_id = Uuid::new_v4();
|
||||
let post_id = Uuid::new_v4().to_string();
|
||||
|
||||
let uri = format!(
|
||||
"https://ferri.amy.mov/users/{}/posts/{}",
|
||||
|
@ -38,10 +41,11 @@ pub async fn new_status(
|
|||
|
||||
let post = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO post (id, user_id, content, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4)
|
||||
INSERT INTO post (id, uri, user_id, content, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
RETURNING *
|
||||
"#,
|
||||
post_id,
|
||||
uri,
|
||||
id,
|
||||
status.status,
|
||||
|
@ -102,3 +106,137 @@ pub async fn new_status(
|
|||
outbox.post(req).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/statuses", data = "<status>", rank = 2)]
|
||||
pub async fn new_status_json(
|
||||
mut db: Connection<Db>,
|
||||
http: &State<HttpClient>,
|
||||
status: Json<Status>,
|
||||
user: AuthenticatedUser,
|
||||
) -> Json<TimelineStatus> {
|
||||
dbg!(&user);
|
||||
let user = ap::User::from_id(&user.username, &mut **db).await;
|
||||
let outbox = ap::Outbox::for_user(user.clone(), http);
|
||||
|
||||
let post_id = Uuid::new_v4().to_string();
|
||||
|
||||
let uri = format!(
|
||||
"https://ferri.amy.mov/users/{}/posts/{}",
|
||||
user.id(),
|
||||
post_id
|
||||
);
|
||||
let id = user.id();
|
||||
let now = Local::now().to_rfc3339();
|
||||
|
||||
let post = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO post (id, uri, user_id, content, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
RETURNING *
|
||||
"#,
|
||||
post_id,
|
||||
uri,
|
||||
id,
|
||||
status.status,
|
||||
now
|
||||
)
|
||||
.fetch_one(&mut **db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let actors = sqlx::query!("SELECT * FROM actor")
|
||||
.fetch_all(&mut **db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for record in actors {
|
||||
// Don't send to ourselves
|
||||
if &record.id == user.actor_id() {
|
||||
continue
|
||||
}
|
||||
|
||||
let create_id = format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4());
|
||||
|
||||
let activity = ap::Activity {
|
||||
id: create_id,
|
||||
ty: ap::ActivityType::Create,
|
||||
object: content::Post {
|
||||
context: "https://www.w3.org/ns/activitystreams".to_string(),
|
||||
id: uri.clone(),
|
||||
content: status.status.clone(),
|
||||
ty: "Note".to_string(),
|
||||
ts: Local::now().to_rfc3339(),
|
||||
to: vec![format!(
|
||||
"https://ferri.amy.mov/users/{}/followers",
|
||||
user.username()
|
||||
)],
|
||||
cc: vec!["https://www.w3.org/ns/activitystreams#Public".to_string()],
|
||||
},
|
||||
to: vec![format!(
|
||||
"https://ferri.amy.mov/users/{}/followers",
|
||||
user.username()
|
||||
)],
|
||||
cc: vec!["https://www.w3.org/ns/activitystreams#Public".to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let actor = ap::Actor::from_raw(
|
||||
record.id.clone(),
|
||||
record.inbox.clone(),
|
||||
record.outbox.clone(),
|
||||
);
|
||||
let req = ap::OutgoingActivity {
|
||||
req: activity,
|
||||
signed_by: format!("https://ferri.amy.mov/users/{}#main-key", user.username()),
|
||||
to: actor,
|
||||
};
|
||||
|
||||
req.save(&mut **db).await;
|
||||
outbox.post(req).await;
|
||||
}
|
||||
|
||||
let user_uri = format!(
|
||||
"https://ferri.amy.mov/users/{}",
|
||||
user.id(),
|
||||
);
|
||||
Json(TimelineStatus {
|
||||
id: post.id.clone(),
|
||||
created_at: post.created_at.clone(),
|
||||
in_reply_to_id: None,
|
||||
in_reply_to_account_id: None,
|
||||
content: post.content.clone(),
|
||||
visibility: "public".to_string(),
|
||||
spoiler_text: "".to_string(),
|
||||
sensitive: false,
|
||||
uri: post.uri.clone(),
|
||||
url: post.uri.clone(),
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
favourited: false,
|
||||
reblogged: false,
|
||||
muted: false,
|
||||
bookmarked: false,
|
||||
media_attachments: vec![],
|
||||
account: CredentialAcount {
|
||||
id: user.id().to_string(),
|
||||
username: user.username().to_string(),
|
||||
acct: user.username().to_string(),
|
||||
display_name: user.display_name().to_string(),
|
||||
locked: false,
|
||||
bot: false,
|
||||
created_at: "2025-04-10T22:12:09Z".to_string(),
|
||||
attribution_domains: vec![],
|
||||
note: "".to_string(),
|
||||
url: user_uri,
|
||||
avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
followers_count: 1,
|
||||
following_count: 1,
|
||||
statuses_count: 1,
|
||||
last_status_at: "2025-04-10T22:14:34Z".to_string(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,32 +10,33 @@ pub type TimelineAccount = CredentialAcount;
|
|||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct TimelineStatus {
|
||||
id: String,
|
||||
created_at: String,
|
||||
in_reply_to_id: Option<String>,
|
||||
in_reply_to_account_id: Option<String>,
|
||||
content: String,
|
||||
visibility: String,
|
||||
spoiler_text: String,
|
||||
sensitive: bool,
|
||||
uri: String,
|
||||
url: String,
|
||||
replies_count: i64,
|
||||
reblogs_count: i64,
|
||||
favourites_count: i64,
|
||||
favourited: bool,
|
||||
reblogged: bool,
|
||||
muted: bool,
|
||||
bookmarked: bool,
|
||||
media_attachments: Vec<()>,
|
||||
account: TimelineAccount,
|
||||
pub id: String,
|
||||
pub created_at: String,
|
||||
pub in_reply_to_id: Option<String>,
|
||||
pub in_reply_to_account_id: Option<String>,
|
||||
pub content: String,
|
||||
pub visibility: String,
|
||||
pub spoiler_text: String,
|
||||
pub sensitive: bool,
|
||||
pub uri: String,
|
||||
pub url: String,
|
||||
pub replies_count: i64,
|
||||
pub reblogs_count: i64,
|
||||
pub favourites_count: i64,
|
||||
pub favourited: bool,
|
||||
pub reblogged: bool,
|
||||
pub muted: bool,
|
||||
pub bookmarked: bool,
|
||||
pub media_attachments: Vec<()>,
|
||||
pub account: TimelineAccount,
|
||||
}
|
||||
|
||||
#[get("/timelines/home?<limit>")]
|
||||
pub async fn home(mut db: Connection<Db>, limit: i64) -> Json<Vec<TimelineStatus>> {
|
||||
let posts = sqlx::query!(
|
||||
r#"
|
||||
SELECT p.id as "post_id", u.id as "user_id", p.content, u.username, u.display_name, u.actor_id FROM post p
|
||||
SELECT p.id as "post_id", u.id as "user_id", p.content, p.uri as "post_uri", u.username, u.display_name, u.actor_id, p.created_at
|
||||
FROM post p
|
||||
INNER JOIN user u on p.user_id = u.id
|
||||
"#
|
||||
)
|
||||
|
@ -45,17 +46,18 @@ pub async fn home(mut db: Connection<Db>, limit: i64) -> Json<Vec<TimelineStatus
|
|||
|
||||
let mut out = Vec::<TimelineStatus>::new();
|
||||
for record in posts {
|
||||
let user_uri = format!("https://ferri.amy.mov/users/{}", record.username);
|
||||
out.push(TimelineStatus {
|
||||
id: record.post_id.clone(),
|
||||
created_at: "2025-04-10T22:12:09Z".to_string(),
|
||||
created_at: record.created_at.clone(),
|
||||
in_reply_to_id: None,
|
||||
in_reply_to_account_id: None,
|
||||
content: record.content.clone(),
|
||||
visibility: "public".to_string(),
|
||||
spoiler_text: "".to_string(),
|
||||
sensitive: false,
|
||||
uri: record.post_id.clone(),
|
||||
url: record.post_id.clone(),
|
||||
uri: record.post_uri.clone(),
|
||||
url: record.post_uri.clone(),
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
|
@ -65,7 +67,7 @@ pub async fn home(mut db: Connection<Db>, limit: i64) -> Json<Vec<TimelineStatus
|
|||
bookmarked: false,
|
||||
media_attachments: vec![],
|
||||
account: CredentialAcount {
|
||||
id: record.actor_id.clone(),
|
||||
id: record.user_id.clone(),
|
||||
username: record.username.clone(),
|
||||
acct: record.username.clone(),
|
||||
display_name: record.display_name.clone(),
|
||||
|
@ -74,7 +76,7 @@ pub async fn home(mut db: Connection<Db>, limit: i64) -> Json<Vec<TimelineStatus
|
|||
created_at: "2025-04-10T22:12:09Z".to_string(),
|
||||
attribution_domains: vec![],
|
||||
note: "".to_string(),
|
||||
url: record.actor_id.clone(),
|
||||
url: user_uri,
|
||||
avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
|
|
|
@ -7,6 +7,7 @@ use rocket_db_pools::Connection;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{AuthenticatedUser, Db, http::HttpClient};
|
||||
use crate::timeline::{TimelineStatus, TimelineAccount};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
|
@ -44,10 +45,10 @@ pub async fn verify_credentials() -> Json<CredentialAcount> {
|
|||
attribution_domains: vec![],
|
||||
note: "".to_string(),
|
||||
url: "https://ferri.amy.mov/@amy".to_string(),
|
||||
avatar: "https://i.sstatic.net/l60Hf.png".to_string(),
|
||||
avatar_static: "https://i.sstatic.net/l60Hf.png".to_string(),
|
||||
header: "https://i.sstatic.net/l60Hf.png".to_string(),
|
||||
header_static: "https://i.sstatic.net/l60Hf.png".to_string(),
|
||||
avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
followers_count: 1,
|
||||
following_count: 1,
|
||||
statuses_count: 1,
|
||||
|
@ -55,15 +56,15 @@ pub async fn verify_credentials() -> Json<CredentialAcount> {
|
|||
})
|
||||
}
|
||||
|
||||
#[post("/accounts/<account>/follow")]
|
||||
#[post("/accounts/<uuid>/follow")]
|
||||
pub async fn new_follow(
|
||||
mut db: Connection<Db>,
|
||||
http: &State<HttpClient>,
|
||||
account: &str,
|
||||
uuid: &str,
|
||||
user: AuthenticatedUser,
|
||||
) {
|
||||
let follower = ap::User::from_actor_id(&user.actor_id, &mut **db).await;
|
||||
let followed = ap::User::from_username(account, &mut **db).await;
|
||||
let followed = ap::User::from_id(uuid, &mut **db).await;
|
||||
|
||||
let outbox = ap::Outbox::for_user(follower.clone(), http.inner());
|
||||
|
||||
|
@ -86,3 +87,98 @@ pub async fn new_follow(
|
|||
req.save(&mut **db).await;
|
||||
outbox.post(req).await;
|
||||
}
|
||||
|
||||
#[get("/accounts/<uuid>")]
|
||||
pub async fn account(mut db: Connection<Db>, uuid: &str, user: AuthenticatedUser) -> Json<TimelineAccount> {
|
||||
let user = ap::User::from_id(uuid, &mut **db).await;
|
||||
let user_uri = format!("https://ferri.amy.mov/users/{}", user.username());
|
||||
Json(CredentialAcount {
|
||||
id: user.id().to_string(),
|
||||
username: user.username().to_string(),
|
||||
acct: user.username().to_string(),
|
||||
display_name: user.display_name().to_string(),
|
||||
locked: false,
|
||||
bot: false,
|
||||
created_at: "2025-04-10T22:12:09Z".to_string(),
|
||||
attribution_domains: vec![],
|
||||
note: "".to_string(),
|
||||
url: user_uri,
|
||||
avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
followers_count: 1,
|
||||
following_count: 1,
|
||||
statuses_count: 1,
|
||||
last_status_at: "2025-04-10T22:14:34Z".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[get("/accounts/<uuid>/statuses?<limit>")]
|
||||
pub async fn statuses(
|
||||
mut db: Connection<Db>,
|
||||
uuid: &str,
|
||||
limit: Option<i64>,
|
||||
user: AuthenticatedUser,
|
||||
) -> Json<Vec<TimelineStatus>> {
|
||||
let user = ap::User::from_id(uuid, &mut **db).await;
|
||||
|
||||
let uid = user.id();
|
||||
let posts = sqlx::query!(
|
||||
r#"
|
||||
SELECT p.id as "post_id", u.id as "user_id", p.content, p.uri as "post_uri", u.username, u.display_name, u.actor_id, p.created_at
|
||||
FROM post p
|
||||
INNER JOIN user u on p.user_id = u.id
|
||||
WHERE u.id = ?1
|
||||
"#, uid)
|
||||
.fetch_all(&mut **db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut out = Vec::<TimelineStatus>::new();
|
||||
for record in posts {
|
||||
let user_uri = format!("https://ferri.amy.mov/users/{}", record.username);
|
||||
out.push(TimelineStatus {
|
||||
id: record.post_id.clone(),
|
||||
created_at: record.created_at.clone(),
|
||||
in_reply_to_id: None,
|
||||
in_reply_to_account_id: None,
|
||||
content: record.content.clone(),
|
||||
visibility: "public".to_string(),
|
||||
spoiler_text: "".to_string(),
|
||||
sensitive: false,
|
||||
uri: record.post_uri.clone(),
|
||||
url: record.post_uri.clone(),
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
favourited: false,
|
||||
reblogged: false,
|
||||
muted: false,
|
||||
bookmarked: false,
|
||||
media_attachments: vec![],
|
||||
account: CredentialAcount {
|
||||
id: record.user_id.clone(),
|
||||
username: record.username.clone(),
|
||||
acct: record.username.clone(),
|
||||
display_name: record.display_name.clone(),
|
||||
locked: false,
|
||||
bot: false,
|
||||
created_at: "2025-04-10T22:12:09Z".to_string(),
|
||||
attribution_domains: vec![],
|
||||
note: "".to_string(),
|
||||
url: user_uri,
|
||||
avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(),
|
||||
followers_count: 1,
|
||||
following_count: 1,
|
||||
statuses_count: 1,
|
||||
last_status_at: "2025-04-10T22:14:34Z".to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Json(out)
|
||||
}
|
||||
|
|
|
@ -111,6 +111,13 @@ async fn handle_follow_activity(followed_account: String, activity: activity::Fo
|
|||
outbox.post(req).await;
|
||||
}
|
||||
|
||||
async fn handle_like_activity(activity: activity::LikeActivity, mut db: Connection<Db>) {
|
||||
let target_post = sqlx::query!("SELECT * FROM post WHERE uri = ?1", activity.object)
|
||||
.fetch_one(&mut **db)
|
||||
.await.unwrap();
|
||||
dbg!(&target_post);
|
||||
}
|
||||
|
||||
async fn handle_create_activity(activity: activity::CreateActivity,http: &HttpClient, mut db: Connection<Db>) {
|
||||
assert!(&activity.object.ty == "Note");
|
||||
let user = http
|
||||
|
@ -128,21 +135,17 @@ async fn handle_create_activity(activity: activity::CreateActivity,http: &HttpCl
|
|||
|
||||
let user = ap::User::from_actor_id(&activity.actor, &mut **db).await;
|
||||
|
||||
let post_id = Uuid::new_v4();
|
||||
|
||||
let uri = format!(
|
||||
"https://ferri.amy.mov/users/{}/posts/{}",
|
||||
user.username(),
|
||||
post_id
|
||||
);
|
||||
let id = user.id();
|
||||
let user_id = user.id();
|
||||
let now = Local::now().to_rfc3339();
|
||||
let content = activity.object.content.clone();
|
||||
let post_id = Uuid::new_v4().to_string();
|
||||
let uri = activity.id;
|
||||
|
||||
|
||||
sqlx::query!(r#"
|
||||
INSERT INTO post (id, user_id, content, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4)
|
||||
"#, uri, id, content, now)
|
||||
INSERT INTO post (id, uri, user_id, content, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
"#, post_id, uri, user_id, content, now)
|
||||
.execute(&mut **db)
|
||||
.await.unwrap();
|
||||
}
|
||||
|
@ -163,6 +166,10 @@ pub async fn inbox(db: Connection<Db>, http: &State<HttpClient>, user: String, b
|
|||
let activity = serde_json::from_str::<activity::CreateActivity>(&body).unwrap();
|
||||
handle_create_activity(activity, http.inner(), db).await;
|
||||
},
|
||||
"Like" => {
|
||||
let activity = serde_json::from_str::<activity::LikeActivity>(&body).unwrap();
|
||||
handle_like_activity(activity, db).await;
|
||||
},
|
||||
unknown => {
|
||||
eprintln!("WARN: Unknown activity '{}' - {}", unknown, body);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ pub struct Token {
|
|||
#[post("/oauth/token")]
|
||||
pub async fn new_token() -> Json<Token> {
|
||||
Json(Token {
|
||||
access_token: "access-token".to_string(),
|
||||
access_token: "9b9d497b-2731-435f-a929-e609ca69dac9".to_string(),
|
||||
token_type: "Bearer".to_string(),
|
||||
expires_in: 3600,
|
||||
scope: "read write follow push".to_string(),
|
||||
|
|
|
@ -31,9 +31,9 @@ pub async fn outbox(user: String) -> Json<OrderedCollection> {
|
|||
})
|
||||
}
|
||||
|
||||
#[get("/users/<user>/followers")]
|
||||
pub async fn followers(mut db: Connection<Db>, user: String) -> Json<OrderedCollection> {
|
||||
let target = ap::User::from_username(&user, &mut **db).await;
|
||||
#[get("/users/<uuid>/followers")]
|
||||
pub async fn followers(mut db: Connection<Db>, uuid: &str) -> Json<OrderedCollection> {
|
||||
let target = ap::User::from_id(uuid, &mut **db).await;
|
||||
let actor_id = target.actor_id();
|
||||
|
||||
let followers = sqlx::query!(
|
||||
|
@ -49,7 +49,7 @@ pub async fn followers(mut db: Connection<Db>, user: String) -> Json<OrderedColl
|
|||
|
||||
Json(OrderedCollection {
|
||||
ty: "OrderedCollection".to_string(),
|
||||
summary: format!("Followers for {}", user),
|
||||
summary: format!("Followers for {}", uuid),
|
||||
total_items: 1,
|
||||
ordered_items: followers
|
||||
.into_iter()
|
||||
|
@ -58,9 +58,9 @@ pub async fn followers(mut db: Connection<Db>, user: String) -> Json<OrderedColl
|
|||
})
|
||||
}
|
||||
|
||||
#[get("/users/<user>/following")]
|
||||
pub async fn following(mut db: Connection<Db>, user: String) -> Json<OrderedCollection> {
|
||||
let target = ap::User::from_username(&user, &mut **db).await;
|
||||
#[get("/users/<uuid>/following")]
|
||||
pub async fn following(mut db: Connection<Db>, uuid: &str) -> Json<OrderedCollection> {
|
||||
let target = ap::User::from_id(uuid, &mut **db).await;
|
||||
let actor_id = target.actor_id();
|
||||
|
||||
let following = sqlx::query!(
|
||||
|
@ -76,7 +76,7 @@ pub async fn following(mut db: Connection<Db>, user: String) -> Json<OrderedColl
|
|||
|
||||
Json(OrderedCollection {
|
||||
ty: "OrderedCollection".to_string(),
|
||||
summary: format!("Following for {}", user),
|
||||
summary: format!("Following for {}", uuid),
|
||||
total_items: 1,
|
||||
ordered_items: following
|
||||
.into_iter()
|
||||
|
@ -85,40 +85,47 @@ pub async fn following(mut db: Connection<Db>, user: String) -> Json<OrderedColl
|
|||
})
|
||||
}
|
||||
|
||||
#[get("/users/<user>/posts/<post>")]
|
||||
pub async fn post(user: String, post: String) -> (ContentType, Json<content::Post>) {
|
||||
#[get("/users/<uuid>/posts/<post>")]
|
||||
pub async fn post(mut db: Connection<Db>, uuid: &str, post: String) -> (ContentType, Json<content::Post>) {
|
||||
let post = sqlx::query!(r#"
|
||||
SELECT * FROM post WHERE id = ?1
|
||||
"#, post)
|
||||
.fetch_one(&mut **db)
|
||||
.await.unwrap();
|
||||
|
||||
(
|
||||
activity_type(),
|
||||
Json(content::Post {
|
||||
id: format!("https://ferri.amy.mov/users/{}/posts/{}", user, post),
|
||||
context: "https://www.w3.org/ns/activitystreams".to_string(),
|
||||
id: format!("https://ferri.amy.mov/users/{}/posts/{}", uuid, post.id),
|
||||
ty: "Note".to_string(),
|
||||
content: "My first post".to_string(),
|
||||
ts: "2025-04-10T10:48:11Z".to_string(),
|
||||
content: post.content,
|
||||
ts: post.created_at,
|
||||
to: vec!["https://ferri.amy.mov/users/amy/followers".to_string()],
|
||||
cc: vec!["https://www.w3.org/ns/activitystreams#Public".to_string()],
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[get("/users/<user>")]
|
||||
pub async fn user(user: String) -> (ContentType, Json<Person>) {
|
||||
#[get("/users/<uuid>")]
|
||||
pub async fn user(mut db: Connection<Db>, uuid: &str) -> (ContentType, Json<Person>) {
|
||||
let user = ap::User::from_id(uuid, &mut **db).await;
|
||||
(
|
||||
activity_type(),
|
||||
Json(Person {
|
||||
context: "https://www.w3.org/ns/activitystreams".to_string(),
|
||||
ty: "Person".to_string(),
|
||||
id: format!("https://ferri.amy.mov/users/{}", user),
|
||||
name: user.clone(),
|
||||
preferred_username: user.clone(),
|
||||
followers: format!("https://ferri.amy.mov/users/{}/followers", user),
|
||||
following: format!("https://ferri.amy.mov/users/{}/following", user),
|
||||
summary: format!("ferri {}", user),
|
||||
inbox: format!("https://ferri.amy.mov/users/{}/inbox", user),
|
||||
outbox: format!("https://ferri.amy.mov/users/{}/outbox", user),
|
||||
id: user.id().to_string(),
|
||||
name: user.username().to_string(),
|
||||
preferred_username: user.display_name().to_string(),
|
||||
followers: format!("https://ferri.amy.mov/users/{}/followers", uuid),
|
||||
following: format!("https://ferri.amy.mov/users/{}/following", uuid),
|
||||
summary: format!("ferri {}", user.username()),
|
||||
inbox: format!("https://ferri.amy.mov/users/{}/inbox", uuid),
|
||||
outbox: format!("https://ferri.amy.mov/users/{}/outbox", uuid),
|
||||
public_key: Some(UserKey {
|
||||
id: format!("https://ferri.amy.mov/users/{}#main-key", user),
|
||||
owner: format!("https://ferri.amy.mov/users/{}", user),
|
||||
id: format!("https://ferri.amy.mov/users/{}#main-key", uuid),
|
||||
owner: format!("https://ferri.amy.mov/users/{}", uuid),
|
||||
public_key: include_str!("../../../public.pem").to_string(),
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -25,7 +25,7 @@ pub async fn webfinger(mut db: Connection<Db>, resource: &str) -> Json<Webfinger
|
|||
Json(WebfingerResponse {
|
||||
subject: resource.to_string(),
|
||||
aliases: vec![
|
||||
format!("https://ferri.amy.mov/users/{}", user.username()),
|
||||
format!("https://ferri.amy.mov/users/{}", user.id()),
|
||||
format!("https://ferri.amy.mov/@{}", user.username()),
|
||||
],
|
||||
links: vec![
|
||||
|
@ -37,7 +37,7 @@ pub async fn webfinger(mut db: Connection<Db>, resource: &str) -> Json<Webfinger
|
|||
Link {
|
||||
rel: "self".to_string(),
|
||||
ty: Some("application/activity+json".to_string()),
|
||||
href: Some(format!("https://ferri.amy.mov/users/{}", user.username())),
|
||||
href: Some(format!("https://ferri.amy.mov/users/{}", user.id())),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
|
|
@ -41,6 +41,7 @@ impl<'a> FromRequest<'a> for AuthenticatedUser {
|
|||
type Error = LoginError;
|
||||
async fn from_request(request: &'a Request<'_>) -> Outcome<AuthenticatedUser, LoginError> {
|
||||
let token = request.headers().get_one("Authorization").unwrap();
|
||||
let token = token.strip_prefix("Bearer").map(|s| s.trim()).unwrap_or(token);
|
||||
Outcome::Success(AuthenticatedUser {
|
||||
username: token.to_string(),
|
||||
actor_id: format!("https://ferri.amy.mov/users/{}", token)
|
||||
|
@ -80,7 +81,10 @@ pub fn launch() -> Rocket<Build> {
|
|||
"/api/v1",
|
||||
routes![
|
||||
api::status::new_status,
|
||||
api::status::new_status_json,
|
||||
api::user::new_follow,
|
||||
api::user::statuses,
|
||||
api::user::account,
|
||||
api::apps::new_app,
|
||||
api::preferences::preferences,
|
||||
api::user::verify_credentials,
|
||||
|
|
|
@ -10,9 +10,12 @@ pub struct MinimalActivity {
|
|||
pub ty: String,
|
||||
}
|
||||
|
||||
pub type DeleteActivity = BasicActivity;
|
||||
pub type LikeActivity = BasicActivity;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct DeleteActivity {
|
||||
pub struct BasicActivity {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub ty: String,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS actor
|
||||
(
|
||||
-- URI
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
inbox TEXT NOT NULL,
|
||||
outbox TEXT NOT NULL
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS post
|
||||
(
|
||||
-- Uri
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
uri TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
|
|
|
@ -2,8 +2,8 @@ CREATE TABLE IF NOT EXISTS activity
|
|||
(
|
||||
-- UUID
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
ty TEXT NOT NULL,
|
||||
actor_id TEXT NOT NULL,
|
||||
ty TEXT NOT NULL,
|
||||
actor_id TEXT NOT NULL,
|
||||
|
||||
FOREIGN KEY(actor_id) REFERENCES actor(id)
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue