mirror of
https://github.com/nullishamy/ferri.git
synced 2025-06-29 01:24:17 +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
|
@ -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(),
|
||||
|
@ -86,6 +88,6 @@ pub async fn home(mut db: Connection<Db>, limit: i64) -> Json<Vec<TimelineStatus
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Json(out)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue