Add support for search by Note url
This commit is contained in:
parent
284840463c
commit
81d6cf3daf
5 changed files with 65 additions and 13 deletions
|
@ -3,6 +3,6 @@ pub mod actor;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod deliverer;
|
pub mod deliverer;
|
||||||
pub mod fetcher;
|
pub mod fetcher;
|
||||||
mod receiver;
|
pub mod receiver;
|
||||||
pub mod views;
|
pub mod views;
|
||||||
mod vocabulary;
|
mod vocabulary;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::config::Config;
|
||||||
use crate::database::{Pool, get_database_client};
|
use crate::database::{Pool, get_database_client};
|
||||||
use crate::errors::{DatabaseError, HttpError, ValidationError};
|
use crate::errors::{DatabaseError, HttpError, ValidationError};
|
||||||
use crate::models::attachments::queries::create_attachment;
|
use crate::models::attachments::queries::create_attachment;
|
||||||
use crate::models::posts::types::PostCreateData;
|
use crate::models::posts::types::{Post, PostCreateData};
|
||||||
use crate::models::posts::queries::{
|
use crate::models::posts::queries::{
|
||||||
create_post,
|
create_post,
|
||||||
get_post_by_id,
|
get_post_by_id,
|
||||||
|
@ -101,9 +101,18 @@ pub async fn process_note(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_client: &mut impl GenericClient,
|
db_client: &mut impl GenericClient,
|
||||||
object: Object,
|
object: Object,
|
||||||
) -> Result<(), HttpError> {
|
) -> Result<Post, HttpError> {
|
||||||
|
match get_post_by_object_id(db_client, &object.id).await {
|
||||||
|
Ok(post) => return Ok(post), // post already exists
|
||||||
|
Err(DatabaseError::NotFound(_)) => (), // continue processing
|
||||||
|
Err(other_error) => return Err(other_error.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let initial_object_id = object.id.clone();
|
||||||
let mut maybe_parent_object_id = object.in_reply_to.clone();
|
let mut maybe_parent_object_id = object.in_reply_to.clone();
|
||||||
let mut objects = vec![object];
|
let mut objects = vec![object];
|
||||||
|
let mut posts = vec![];
|
||||||
|
|
||||||
// Fetch ancestors by going through inReplyTo references
|
// Fetch ancestors by going through inReplyTo references
|
||||||
// TODO: fetch replies too
|
// TODO: fetch replies too
|
||||||
loop {
|
loop {
|
||||||
|
@ -133,6 +142,7 @@ pub async fn process_note(
|
||||||
maybe_parent_object_id = object.in_reply_to.clone();
|
maybe_parent_object_id = object.in_reply_to.clone();
|
||||||
objects.push(object);
|
objects.push(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objects are ordered according to their place in reply tree,
|
// Objects are ordered according to their place in reply tree,
|
||||||
// starting with the root
|
// starting with the root
|
||||||
objects.reverse();
|
objects.reverse();
|
||||||
|
@ -189,9 +199,14 @@ pub async fn process_note(
|
||||||
object_id: Some(object.id),
|
object_id: Some(object.id),
|
||||||
created_at: object.published,
|
created_at: object.published,
|
||||||
};
|
};
|
||||||
create_post(db_client, &author.id, post_data).await?;
|
let post = create_post(db_client, &author.id, post_data).await?;
|
||||||
|
posts.push(post);
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
let initial_post = posts.into_iter()
|
||||||
|
.find(|post| post.object_id.as_ref() == Some(&initial_object_id))
|
||||||
|
.unwrap();
|
||||||
|
Ok(initial_post)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn receive_activity(
|
pub async fn receive_activity(
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use tokio_postgres::GenericClient;
|
use tokio_postgres::GenericClient;
|
||||||
|
|
||||||
use crate::activitypub::fetcher::fetch_profile;
|
use crate::activitypub::fetcher::{fetch_object, fetch_profile};
|
||||||
|
use crate::activitypub::receiver::process_note;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::errors::{ValidationError, HttpError};
|
use crate::errors::{ValidationError, HttpError};
|
||||||
use crate::mastodon_api::accounts::types::Account;
|
use crate::mastodon_api::accounts::types::Account;
|
||||||
|
use crate::mastodon_api::statuses::types::Status;
|
||||||
|
use crate::models::posts::types::Post;
|
||||||
use crate::models::profiles::queries::{create_profile, search_profile};
|
use crate::models::profiles::queries::{create_profile, search_profile};
|
||||||
use crate::models::profiles::types::DbActorProfile;
|
use crate::models::profiles::types::DbActorProfile;
|
||||||
use super::types::SearchResults;
|
use super::types::SearchResults;
|
||||||
|
|
||||||
fn parse_search_query(query: &str) ->
|
fn parse_profile_query(query: &str) ->
|
||||||
Result<(String, Option<String>), ValidationError>
|
Result<(String, Option<String>), ValidationError>
|
||||||
{
|
{
|
||||||
let acct_regexp = Regex::new(r"^@?(?P<user>\w+)(@(?P<instance>[\w\.-]+))?").unwrap();
|
let acct_regexp = Regex::new(r"^@?(?P<user>\w+)(@(?P<instance>[\w\.-]+))?$").unwrap();
|
||||||
let acct_caps = acct_regexp.captures(query)
|
let acct_caps = acct_regexp.captures(query)
|
||||||
.ok_or(ValidationError("invalid search query"))?;
|
.ok_or(ValidationError("invalid search query"))?;
|
||||||
let username = acct_caps.name("user")
|
let username = acct_caps.name("user")
|
||||||
|
@ -28,7 +31,13 @@ async fn search_profiles(
|
||||||
db_client: &impl GenericClient,
|
db_client: &impl GenericClient,
|
||||||
search_query: &str,
|
search_query: &str,
|
||||||
) -> Result<Vec<DbActorProfile>, HttpError> {
|
) -> Result<Vec<DbActorProfile>, HttpError> {
|
||||||
let (username, instance) = parse_search_query(search_query)?;
|
let (username, instance) = match parse_profile_query(search_query) {
|
||||||
|
Ok(parsed) => parsed,
|
||||||
|
Err(_) => {
|
||||||
|
// Not an 'acct' query
|
||||||
|
return Ok(vec![]);
|
||||||
|
},
|
||||||
|
};
|
||||||
let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?;
|
let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?;
|
||||||
if profiles.len() == 0 && instance.is_some() {
|
if profiles.len() == 0 && instance.is_some() {
|
||||||
let instance_uri = instance.unwrap();
|
let instance_uri = instance.unwrap();
|
||||||
|
@ -50,14 +59,40 @@ async fn search_profiles(
|
||||||
Ok(profiles)
|
Ok(profiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn search_note(
|
||||||
|
config: &Config,
|
||||||
|
db_client: &mut impl GenericClient,
|
||||||
|
search_query: &str,
|
||||||
|
) -> Result<Option<Post>, HttpError> {
|
||||||
|
if url::Url::parse(search_query).is_err() {
|
||||||
|
// Not a valid URL
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let maybe_post = if let Ok(object) = fetch_object(search_query).await {
|
||||||
|
let post = process_note(config, db_client, object).await?;
|
||||||
|
Some(post)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(maybe_post)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn search(
|
pub async fn search(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_client: &impl GenericClient,
|
db_client: &mut impl GenericClient,
|
||||||
search_query: &str,
|
search_query: &str,
|
||||||
) -> Result<SearchResults, HttpError> {
|
) -> Result<SearchResults, HttpError> {
|
||||||
let profiles = search_profiles(config, db_client, search_query).await?;
|
let profiles = search_profiles(config, db_client, search_query).await?;
|
||||||
let accounts: Vec<Account> = profiles.into_iter()
|
let accounts: Vec<Account> = profiles.into_iter()
|
||||||
.map(|profile| Account::from_profile(profile, &config.instance_url()))
|
.map(|profile| Account::from_profile(profile, &config.instance_url()))
|
||||||
.collect();
|
.collect();
|
||||||
Ok(SearchResults { accounts })
|
let maybe_post = search_note(config, db_client, search_query).await?;
|
||||||
|
let statuses = match maybe_post {
|
||||||
|
Some(post) => {
|
||||||
|
let status = Status::from_post(post, &config.instance_url());
|
||||||
|
vec![status]
|
||||||
|
},
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
Ok(SearchResults { accounts, statuses })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::mastodon_api::accounts::types::Account;
|
use crate::mastodon_api::accounts::types::Account;
|
||||||
|
use crate::mastodon_api::statuses::types::Status;
|
||||||
|
|
||||||
/// https://docs.joinmastodon.org/methods/search/
|
/// https://docs.joinmastodon.org/methods/search/
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -11,4 +12,5 @@ pub struct SearchQueryParams {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct SearchResults {
|
pub struct SearchResults {
|
||||||
pub accounts: Vec<Account>,
|
pub accounts: Vec<Account>,
|
||||||
|
pub statuses: Vec<Status>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ async fn search_view(
|
||||||
db_pool: web::Data<Pool>,
|
db_pool: web::Data<Pool>,
|
||||||
query_params: web::Query<SearchQueryParams>,
|
query_params: web::Query<SearchQueryParams>,
|
||||||
) -> Result<HttpResponse, HttpError> {
|
) -> Result<HttpResponse, HttpError> {
|
||||||
let db_client = &**get_database_client(&db_pool).await?;
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
get_current_user(db_client, auth.token()).await?;
|
get_current_user(db_client, auth.token()).await?;
|
||||||
let results = search(&config, db_client, &query_params.q).await?;
|
let results = search(&config, db_client, &query_params.q.trim()).await?;
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue