mirror of
https://github.com/nullishamy/ferri.git
synced 2025-06-28 00:54:17 +00:00
feat: lots of timeline improvements; icon urls for users
This commit is contained in:
parent
41c0091e98
commit
a924415a74
15 changed files with 379 additions and 43 deletions
|
@ -4,3 +4,4 @@ pub mod preferences;
|
|||
pub mod status;
|
||||
pub mod timeline;
|
||||
pub mod user;
|
||||
pub mod search;
|
||||
|
|
88
ferri-server/src/endpoints/api/search.rs
Normal file
88
ferri-server/src/endpoints/api/search.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use rocket::{
|
||||
get, serde::json::Json, FromFormField, State,
|
||||
};
|
||||
use main::types::{api, get};
|
||||
use rocket_db_pools::Connection;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{info, error};
|
||||
|
||||
use crate::{http_wrapper::HttpWrapper, AuthenticatedUser, Db};
|
||||
|
||||
#[derive(Serialize, Deserialize, FromFormField, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SearchType {
|
||||
Accounts,
|
||||
Hashtags,
|
||||
Statuses,
|
||||
All
|
||||
}
|
||||
|
||||
impl Default for SearchType {
|
||||
fn default() -> Self {
|
||||
Self::All
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SearchResults {
|
||||
statuses: Vec<api::Status>,
|
||||
accounts: Vec<api::Account>,
|
||||
hashtags: Vec<()>
|
||||
}
|
||||
|
||||
#[get("/search?<q>&<type>")]
|
||||
pub async fn search(
|
||||
q: &str,
|
||||
r#type: SearchType,
|
||||
helpers: &State<crate::Helpers>,
|
||||
mut db: Connection<Db>,
|
||||
user: AuthenticatedUser
|
||||
) -> Json<SearchResults> {
|
||||
let ty = r#type;
|
||||
info!("search for {} (ty: {:?})", q, ty);
|
||||
|
||||
let key_id = "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9#main-key";
|
||||
let http = HttpWrapper::new(&helpers.http, key_id);
|
||||
|
||||
let mut accounts = vec![];
|
||||
let mut statuses = vec![];
|
||||
|
||||
match ty {
|
||||
SearchType::Accounts => {
|
||||
let person = {
|
||||
let res = http.get_person(q).await;
|
||||
if let Err(e) = res {
|
||||
error!("could not load user {}: {}", q, e.to_string());
|
||||
None
|
||||
} else {
|
||||
Some(res.unwrap())
|
||||
}
|
||||
};
|
||||
|
||||
let user = get::user_by_actor_uri(person.unwrap().obj.id, &mut **db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
accounts.push(user.into())
|
||||
},
|
||||
SearchType::Statuses => {
|
||||
if q == "me" {
|
||||
let st = get::posts_for_user_id(user.id, &mut **db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for status in st.into_iter() {
|
||||
statuses.push(status.into());
|
||||
}
|
||||
}
|
||||
},
|
||||
SearchType::Hashtags => todo!(),
|
||||
SearchType::All => todo!(),
|
||||
}
|
||||
|
||||
Json(SearchResults {
|
||||
statuses,
|
||||
accounts,
|
||||
hashtags: vec![],
|
||||
})
|
||||
}
|
|
@ -43,7 +43,7 @@ async fn create_status(
|
|||
http: &HttpClient,
|
||||
status: &Status,
|
||||
) -> TimelineStatus {
|
||||
let user = ap::User::from_id(&user.id, &mut **db).await.unwrap();
|
||||
let user = ap::User::from_id(&user.id.0, &mut **db).await.unwrap();
|
||||
let outbox = ap::Outbox::for_user(user.clone(), http);
|
||||
|
||||
let post_id = ap::new_id();
|
||||
|
|
|
@ -35,11 +35,8 @@ pub struct TimelineStatus {
|
|||
#[get("/timelines/home")]
|
||||
pub async fn home(
|
||||
mut db: Connection<Db>,
|
||||
helpers: &State<crate::Helpers>,
|
||||
_user: AuthenticatedUser,
|
||||
) -> Json<Vec<TimelineStatus>> {
|
||||
let config = &helpers.config;
|
||||
|
||||
#[derive(sqlx::FromRow, Debug)]
|
||||
struct Post {
|
||||
is_boost_source: bool,
|
||||
|
@ -51,6 +48,8 @@ pub async fn home(
|
|||
boosted_post_id: Option<String>,
|
||||
display_name: String,
|
||||
username: String,
|
||||
icon_url: String,
|
||||
user_url: String
|
||||
}
|
||||
|
||||
// FIXME: query! can't cope with this. returns a type error
|
||||
|
@ -75,7 +74,7 @@ pub async fn home(
|
|||
)
|
||||
SELECT is_boost_source, 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, p.boosted_post_id
|
||||
u.actor_id, p.created_at, p.boosted_post_id, u.icon_url, u.url as "user_url"
|
||||
FROM get_home_timeline_with_boosts
|
||||
JOIN post p ON p.id = get_home_timeline_with_boosts.id
|
||||
JOIN user u ON u.id = p.user_id;
|
||||
|
@ -90,7 +89,6 @@ pub async fn home(
|
|||
for record in posts.iter() {
|
||||
let mut boost: Option<Box<TimelineStatus>> = None;
|
||||
if let Some(ref boosted_id) = record.boosted_post_id {
|
||||
let user_uri = config.user_url(&record.user_id);
|
||||
let record = posts.iter().find(|p| &p.post_id == boosted_id).unwrap();
|
||||
|
||||
boost = Some(Box::new(TimelineStatus {
|
||||
|
@ -123,11 +121,11 @@ pub async fn home(
|
|||
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(),
|
||||
url: record.user_url.clone(),
|
||||
avatar: record.icon_url.clone(),
|
||||
avatar_static: record.icon_url.clone(),
|
||||
header: record.icon_url.clone(),
|
||||
header_static: record.icon_url.clone(),
|
||||
followers_count: 1,
|
||||
following_count: 1,
|
||||
statuses_count: 1,
|
||||
|
@ -137,7 +135,6 @@ pub async fn home(
|
|||
}
|
||||
|
||||
if !record.is_boost_source {
|
||||
let user_uri = config.user_web_url(&record.username);
|
||||
out.push(TimelineStatus {
|
||||
id: record.post_id.clone(),
|
||||
created_at: record.created_at.clone(),
|
||||
|
@ -168,11 +165,11 @@ pub async fn home(
|
|||
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(),
|
||||
url: record.user_url.clone(),
|
||||
avatar: record.icon_url.clone(),
|
||||
avatar_static: record.icon_url.clone(),
|
||||
header: record.icon_url.clone(),
|
||||
header_static: record.icon_url.clone(),
|
||||
followers_count: 1,
|
||||
following_count: 1,
|
||||
statuses_count: 1,
|
||||
|
|
|
@ -66,7 +66,7 @@ pub async fn new_follow(
|
|||
) -> Result<(), NotFound<String>> {
|
||||
let http = &helpers.http;
|
||||
|
||||
let follower = ap::User::from_actor_id(&user.actor_id, &mut **db).await;
|
||||
let follower = ap::User::from_actor_id(&user.actor_id.0, &mut **db).await;
|
||||
|
||||
let followed = ap::User::from_id(uuid, &mut **db)
|
||||
.await
|
||||
|
|
|
@ -57,6 +57,9 @@ async fn create_user(
|
|||
};
|
||||
|
||||
let url = format!("https://ferri.amy.mov/{}", acct);
|
||||
let icon_url = user.icon.as_ref().map(|ic| ic.url.clone()).unwrap_or(
|
||||
"https://ferri.amy.mov/assets/pfp.png".to_string()
|
||||
);
|
||||
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
// FIXME: Pull from user
|
||||
|
@ -65,10 +68,10 @@ async fn create_user(
|
|||
r#"
|
||||
INSERT INTO user (
|
||||
id, acct, url, remote, username,
|
||||
actor_id, display_name, created_at
|
||||
actor_id, display_name, created_at, icon_url
|
||||
)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
|
||||
ON CONFLICT(actor_id) DO NOTHING;
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||
ON CONFLICT(actor_id) DO NOTHING;
|
||||
"#,
|
||||
uuid,
|
||||
acct,
|
||||
|
@ -77,7 +80,8 @@ async fn create_user(
|
|||
user.preferred_username,
|
||||
actor,
|
||||
user.name,
|
||||
ts
|
||||
ts,
|
||||
icon_url
|
||||
)
|
||||
.execute(conn)
|
||||
.await
|
||||
|
@ -170,6 +174,9 @@ async fn resolve_actor<'a>(
|
|||
remote: remote_info.is_remote,
|
||||
url: remote_info.web_url,
|
||||
created_at: main::ap::now(),
|
||||
icon_url: person.icon.map(|ic| ic.url).unwrap_or(
|
||||
"https://ferri.amy.mov/assets/pfp.png".to_string()
|
||||
),
|
||||
|
||||
posts: db::UserPosts { last_post_at: None },
|
||||
};
|
||||
|
|
|
@ -6,7 +6,6 @@ use rocket::{
|
|||
FromForm,
|
||||
form::Form,
|
||||
get, post,
|
||||
response::Redirect,
|
||||
response::status::BadRequest,
|
||||
response::content::RawHtml,
|
||||
serde::{Deserialize, Serialize, json::Json},
|
||||
|
@ -81,8 +80,7 @@ pub async fn authorize(
|
|||
client_id: &str,
|
||||
scope: &str,
|
||||
redirect_uri: &str,
|
||||
response_type: &str,
|
||||
mut db: Connection<Db>,
|
||||
response_type: &str
|
||||
) -> Result<RawHtml<String>, BadRequest<String>> {
|
||||
if response_type != "code" {
|
||||
error!("unknown response type {}", response_type);
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::http::HttpClient;
|
|||
use main::types::ap;
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
use tracing::{Level, error, event};
|
||||
use tracing::{Level, error, event, info};
|
||||
|
||||
pub struct HttpWrapper<'a> {
|
||||
client: &'a HttpClient,
|
||||
|
@ -54,6 +54,7 @@ impl<'a> HttpWrapper<'a> {
|
|||
}
|
||||
|
||||
let raw_body = raw_body.unwrap();
|
||||
info!("raw body {}", raw_body);
|
||||
let decoded = serde_json::from_str::<T>(&raw_body);
|
||||
|
||||
if let Err(e) = decoded {
|
||||
|
|
|
@ -5,7 +5,7 @@ use endpoints::{
|
|||
|
||||
use tracing_subscriber::fmt;
|
||||
|
||||
use main::ap;
|
||||
use main::{ap, types::{ObjectUri, ObjectUuid}};
|
||||
|
||||
use main::ap::http;
|
||||
use main::config::Config;
|
||||
|
@ -36,9 +36,9 @@ async fn activity_endpoint(_activity: String) {}
|
|||
#[derive(Debug)]
|
||||
pub struct AuthenticatedUser {
|
||||
pub username: String,
|
||||
pub id: String,
|
||||
pub id: ObjectUuid,
|
||||
pub token: String,
|
||||
pub actor_id: String,
|
||||
pub actor_id: ObjectUri,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -72,9 +72,9 @@ impl<'a> FromRequest<'a> for AuthenticatedUser {
|
|||
if let Ok(auth) = auth {
|
||||
return Outcome::Success(AuthenticatedUser {
|
||||
token: auth.token,
|
||||
id: auth.id,
|
||||
id: ObjectUuid(auth.id),
|
||||
username: auth.display_name,
|
||||
actor_id: auth.actor_id,
|
||||
actor_id: ObjectUri(auth.actor_id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +150,10 @@ pub fn launch(cfg: Config) -> Rocket<Build> {
|
|||
user_profile,
|
||||
],
|
||||
)
|
||||
.mount("/api/v2", routes![api::instance::instance])
|
||||
.mount("/api/v2", routes![
|
||||
api::instance::instance,
|
||||
api::search::search,
|
||||
])
|
||||
.mount(
|
||||
"/api/v1",
|
||||
routes![
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue