Refactor some mastodon API modules
This commit is contained in:
parent
a8c3cd8c90
commit
aa1ef71857
16 changed files with 135 additions and 121 deletions
16
src/main.rs
16
src/main.rs
|
@ -11,16 +11,16 @@ use mitra::database::create_pool;
|
|||
use mitra::database::migrate::apply_migrations;
|
||||
use mitra::logger::configure_logger;
|
||||
use mitra::mastodon_api::accounts::views::account_api_scope;
|
||||
use mitra::mastodon_api::directory::views::profile_directory;
|
||||
use mitra::mastodon_api::instance::views as instance_api;
|
||||
use mitra::mastodon_api::directory::views::directory_api_scope;
|
||||
use mitra::mastodon_api::instance::views::instance_api_scope;
|
||||
use mitra::mastodon_api::markers::views::marker_api_scope;
|
||||
use mitra::mastodon_api::media::views::media_api_scope;
|
||||
use mitra::mastodon_api::notifications::views::notification_api_scope;
|
||||
use mitra::mastodon_api::oauth::auth::create_auth_error_handler;
|
||||
use mitra::mastodon_api::oauth::views::oauth_api_scope;
|
||||
use mitra::mastodon_api::search::views::search;
|
||||
use mitra::mastodon_api::search::views::search_api_scope;
|
||||
use mitra::mastodon_api::statuses::views::status_api_scope;
|
||||
use mitra::mastodon_api::timelines::views as timeline_api;
|
||||
use mitra::mastodon_api::timelines::views::timeline_api_scope;
|
||||
use mitra::nodeinfo::views as nodeinfo;
|
||||
use mitra::scheduler;
|
||||
use mitra::webfinger::views as webfinger;
|
||||
|
@ -76,15 +76,15 @@ async fn main() -> std::io::Result<()> {
|
|||
config.media_dir(),
|
||||
))
|
||||
.service(oauth_api_scope())
|
||||
.service(profile_directory)
|
||||
.service(account_api_scope())
|
||||
.service(directory_api_scope())
|
||||
.service(instance_api_scope())
|
||||
.service(marker_api_scope())
|
||||
.service(media_api_scope())
|
||||
.service(notification_api_scope())
|
||||
.service(status_api_scope())
|
||||
.service(instance_api::instance)
|
||||
.service(search)
|
||||
.service(timeline_api::home_timeline)
|
||||
.service(search_api_scope())
|
||||
.service(timeline_api_scope())
|
||||
.service(webfinger::get_descriptor)
|
||||
.service(activitypub_scope())
|
||||
.service(get_object)
|
||||
|
|
|
@ -10,10 +10,10 @@ use crate::activitypub::activity::{
|
|||
use crate::activitypub::deliverer::deliver_activity;
|
||||
use crate::config::Config;
|
||||
use crate::database::{Pool, get_database_client};
|
||||
use crate::errors::HttpError;
|
||||
use crate::errors::{HttpError, ValidationError};
|
||||
use crate::ethereum::gate::is_allowed_user;
|
||||
use crate::mastodon_api::statuses::types::Status;
|
||||
use crate::mastodon_api::oauth::auth::get_current_user;
|
||||
use crate::mastodon_api::users::views::create_user_view;
|
||||
use crate::models::posts::helpers::get_actions_for_posts;
|
||||
use crate::models::posts::queries::get_posts_by_author;
|
||||
use crate::models::profiles::queries::{
|
||||
|
@ -21,8 +21,61 @@ use crate::models::profiles::queries::{
|
|||
update_profile,
|
||||
};
|
||||
use crate::models::relationships::queries as follows;
|
||||
use crate::models::users::queries::{
|
||||
is_valid_invite_code,
|
||||
create_user,
|
||||
};
|
||||
use crate::utils::crypto::{
|
||||
hash_password,
|
||||
generate_private_key,
|
||||
serialize_private_key,
|
||||
};
|
||||
use crate::utils::files::FileError;
|
||||
use super::types::{Account, AccountUpdateData};
|
||||
use super::types::{Account, AccountCreateData, AccountUpdateData};
|
||||
|
||||
#[post("")]
|
||||
pub async fn create_account(
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
account_data: web::Json<AccountCreateData>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||
let user_data = account_data.into_inner().into_user_data();
|
||||
// Validate
|
||||
user_data.clean()?;
|
||||
if !config.registrations_open {
|
||||
let invite_code = user_data.invite_code.as_ref()
|
||||
.ok_or(ValidationError("invite code is required"))?;
|
||||
if !is_valid_invite_code(db_client, &invite_code).await? {
|
||||
Err(ValidationError("invalid invite code"))?;
|
||||
}
|
||||
}
|
||||
if config.ethereum_contract.is_some() {
|
||||
let is_allowed = is_allowed_user(&config, &user_data.wallet_address).await
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
if !is_allowed {
|
||||
Err(ValidationError("not allowed to sign up"))?;
|
||||
}
|
||||
}
|
||||
// Hash password and generate private key
|
||||
let password_hash = hash_password(&user_data.password)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
let private_key = match web::block(move || generate_private_key()).await {
|
||||
Ok(private_key) => private_key,
|
||||
Err(_) => return Err(HttpError::InternalError),
|
||||
};
|
||||
let private_key_pem = serialize_private_key(private_key)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
|
||||
let user = create_user(
|
||||
db_client,
|
||||
user_data,
|
||||
password_hash,
|
||||
private_key_pem,
|
||||
).await?;
|
||||
let account = Account::from_user(user, &config.instance_url());
|
||||
Ok(HttpResponse::Created().json(account))
|
||||
}
|
||||
|
||||
#[get("/{account_id}")]
|
||||
async fn get_account(
|
||||
|
@ -204,7 +257,7 @@ async fn get_account_statuses(
|
|||
pub fn account_api_scope() -> Scope {
|
||||
web::scope("/api/v1/accounts")
|
||||
// Routes without account ID
|
||||
.service(create_user_view)
|
||||
.service(create_account)
|
||||
.service(get_relationships)
|
||||
.service(verify_credentials)
|
||||
.service(update_credentials)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use actix_web::{get, web, HttpResponse};
|
||||
use actix_web::{get, web, HttpResponse, Scope};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
|
||||
use crate::config::Config;
|
||||
|
@ -8,8 +8,8 @@ use crate::mastodon_api::accounts::types::Account;
|
|||
use crate::mastodon_api::oauth::auth::get_current_user;
|
||||
use crate::models::profiles::queries::get_profiles;
|
||||
|
||||
#[get("/api/v1/directory")]
|
||||
pub async fn profile_directory(
|
||||
#[get("")]
|
||||
async fn profile_directory(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
|
@ -22,3 +22,8 @@ pub async fn profile_directory(
|
|||
.collect();
|
||||
Ok(HttpResponse::Ok().json(accounts))
|
||||
}
|
||||
|
||||
pub fn directory_api_scope() -> Scope {
|
||||
web::scope("/api/v1/directory")
|
||||
.service(profile_directory)
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pub mod types;
|
||||
mod types;
|
||||
pub mod views;
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
use actix_web::{get, web, HttpResponse};
|
||||
use actix_web::{get, web, HttpResponse, Scope};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::errors::HttpError;
|
||||
use super::types::Instance;
|
||||
|
||||
#[get("/api/v1/instance")]
|
||||
pub async fn instance(
|
||||
instance_config: web::Data<Config>,
|
||||
#[get("")]
|
||||
async fn instance_view(
|
||||
config: web::Data<Config>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let instance = Instance::from(instance_config.as_ref());
|
||||
let instance = Instance::from(config.as_ref());
|
||||
Ok(HttpResponse::Ok().json(instance))
|
||||
}
|
||||
|
||||
pub fn instance_api_scope() -> Scope {
|
||||
web::scope("/api/v1/instance")
|
||||
.service(instance_view)
|
||||
}
|
||||
|
|
|
@ -8,4 +8,3 @@ pub mod oauth;
|
|||
pub mod search;
|
||||
pub mod statuses;
|
||||
pub mod timelines;
|
||||
pub mod users;
|
||||
|
|
|
@ -4,8 +4,10 @@ use tokio_postgres::GenericClient;
|
|||
use crate::activitypub::fetcher::fetch_profile;
|
||||
use crate::config::Config;
|
||||
use crate::errors::{ValidationError, HttpError};
|
||||
use crate::mastodon_api::accounts::types::Account;
|
||||
use crate::models::profiles::queries::{create_profile, search_profile};
|
||||
use crate::models::profiles::types::DbActorProfile;
|
||||
use super::types::SearchResults;
|
||||
|
||||
fn parse_search_query(query: &str) ->
|
||||
Result<(String, Option<String>), ValidationError>
|
||||
|
@ -21,13 +23,13 @@ fn parse_search_query(query: &str) ->
|
|||
Ok((username, instance))
|
||||
}
|
||||
|
||||
pub async fn search(
|
||||
async fn search_profiles(
|
||||
config: &Config,
|
||||
db_client: &impl GenericClient,
|
||||
search_query: &str,
|
||||
) -> Result<Vec<DbActorProfile>, HttpError> {
|
||||
let (username, instance) = parse_search_query(search_query)?;
|
||||
let mut profiles = search_profile(db_client, &username, &instance).await?;
|
||||
let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?;
|
||||
if profiles.len() == 0 && instance.is_some() {
|
||||
let instance_uri = instance.unwrap();
|
||||
let media_dir = config.media_dir();
|
||||
|
@ -45,3 +47,15 @@ pub async fn search(
|
|||
}
|
||||
Ok(profiles)
|
||||
}
|
||||
|
||||
pub async fn search(
|
||||
config: &Config,
|
||||
db_client: &impl GenericClient,
|
||||
search_query: &str,
|
||||
) -> Result<SearchResults, HttpError> {
|
||||
let profiles = search_profiles(config, db_client, search_query).await?;
|
||||
let accounts: Vec<Account> = profiles.into_iter()
|
||||
.map(|profile| Account::from_profile(profile, &config.instance_url()))
|
||||
.collect();
|
||||
Ok(SearchResults { accounts })
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
pub mod queries;
|
||||
pub mod types;
|
||||
mod helpers;
|
||||
mod types;
|
||||
pub mod views;
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::mastodon_api::accounts::types::Account;
|
||||
|
||||
/// https://docs.joinmastodon.org/methods/search/
|
||||
#[derive(Deserialize)]
|
||||
pub struct SearchQueryParams {
|
||||
pub q: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct SearchResults {
|
||||
pub accounts: Vec<Account>,
|
||||
|
|
|
@ -1,22 +1,15 @@
|
|||
use actix_web::{get, web, HttpResponse};
|
||||
use actix_web::{get, web, HttpResponse, Scope};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::database::{Pool, get_database_client};
|
||||
use crate::errors::HttpError;
|
||||
use crate::mastodon_api::accounts::types::Account;
|
||||
use crate::mastodon_api::oauth::auth::get_current_user;
|
||||
use super::queries;
|
||||
use super::types::SearchResults;
|
||||
use super::helpers::search;
|
||||
use super::types::SearchQueryParams;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SearchQueryParams {
|
||||
q: String,
|
||||
}
|
||||
|
||||
#[get("/api/v2/search")]
|
||||
async fn search(
|
||||
#[get("")]
|
||||
async fn search_view(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
|
@ -24,10 +17,11 @@ async fn search(
|
|||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &**get_database_client(&db_pool).await?;
|
||||
get_current_user(db_client, auth.token()).await?;
|
||||
let profiles = queries::search(&config, db_client, &query_params.q).await?;
|
||||
let accounts: Vec<Account> = profiles.into_iter()
|
||||
.map(|profile| Account::from_profile(profile, &config.instance_url()))
|
||||
.collect();
|
||||
let results = SearchResults { accounts };
|
||||
let results = search(&config, db_client, &query_params.q).await?;
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
pub fn search_api_scope() -> Scope {
|
||||
web::scope("/api/v2/search")
|
||||
.service(search_view)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use actix_web::{get, web, HttpResponse};
|
||||
use actix_web::{get, web, HttpResponse, Scope};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
|
||||
use crate::config::Config;
|
||||
|
@ -7,18 +7,18 @@ use crate::errors::HttpError;
|
|||
use crate::mastodon_api::oauth::auth::get_current_user;
|
||||
use crate::mastodon_api::statuses::types::Status;
|
||||
use crate::models::posts::helpers::get_actions_for_posts;
|
||||
use crate::models::posts::queries::get_posts;
|
||||
use crate::models::posts::queries::get_home_timeline;
|
||||
|
||||
/// https://docs.joinmastodon.org/methods/timelines/
|
||||
#[get("/api/v1/timelines/home")]
|
||||
pub async fn home_timeline(
|
||||
#[get("/home")]
|
||||
async fn home_timeline(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &**get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
let mut posts = get_posts(db_client, ¤t_user.id).await?;
|
||||
let mut posts = get_home_timeline(db_client, ¤t_user.id).await?;
|
||||
get_actions_for_posts(
|
||||
db_client,
|
||||
¤t_user.id,
|
||||
|
@ -30,3 +30,8 @@ pub async fn home_timeline(
|
|||
.collect();
|
||||
Ok(HttpResponse::Ok().json(statuses))
|
||||
}
|
||||
|
||||
pub fn timeline_api_scope() -> Scope {
|
||||
web::scope("/api/v1/timelines")
|
||||
.service(home_timeline)
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub mod views;
|
|
@ -1,65 +0,0 @@
|
|||
use actix_web::{
|
||||
post, web,
|
||||
HttpResponse,
|
||||
};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::database::{Pool, get_database_client};
|
||||
use crate::errors::{HttpError, ValidationError};
|
||||
use crate::ethereum::gate::is_allowed_user;
|
||||
use crate::mastodon_api::accounts::types::{Account, AccountCreateData};
|
||||
use crate::models::users::queries::{
|
||||
is_valid_invite_code,
|
||||
create_user,
|
||||
};
|
||||
use crate::utils::crypto::{
|
||||
hash_password,
|
||||
generate_private_key,
|
||||
serialize_private_key,
|
||||
};
|
||||
|
||||
// /api/v1/accounts
|
||||
#[post("")]
|
||||
pub async fn create_user_view(
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
account_data: web::Json<AccountCreateData>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||
let user_data = account_data.into_inner().into_user_data();
|
||||
// Validate
|
||||
user_data.clean()?;
|
||||
if !config.registrations_open {
|
||||
let invite_code = user_data.invite_code.as_ref()
|
||||
.ok_or(ValidationError("invite code is required"))?;
|
||||
if !is_valid_invite_code(db_client, &invite_code).await? {
|
||||
Err(ValidationError("invalid invite code"))?;
|
||||
}
|
||||
}
|
||||
if config.ethereum_contract.is_some() {
|
||||
let is_allowed = is_allowed_user(&config, &user_data.wallet_address).await
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
if !is_allowed {
|
||||
Err(ValidationError("not allowed to sign up"))?;
|
||||
}
|
||||
}
|
||||
// Hash password and generate private key
|
||||
let password_hash = hash_password(&user_data.password)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
let private_key = match web::block(move || generate_private_key()).await {
|
||||
Ok(private_key) => private_key,
|
||||
Err(_) => return Err(HttpError::InternalError),
|
||||
};
|
||||
let private_key_pem = serialize_private_key(private_key)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
|
||||
let user = create_user(
|
||||
db_client,
|
||||
user_data,
|
||||
password_hash,
|
||||
private_key_pem,
|
||||
).await?;
|
||||
let account = Account::from_user(user, &config.instance_url());
|
||||
Ok(HttpResponse::Created().json(account))
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ use crate::models::notifications::queries::create_reply_notification;
|
|||
use crate::models::profiles::queries::update_post_count;
|
||||
use super::types::{DbPost, Post, PostCreateData};
|
||||
|
||||
pub async fn get_posts(
|
||||
pub async fn get_home_timeline(
|
||||
db_client: &impl GenericClient,
|
||||
current_user_id: &Uuid,
|
||||
) -> Result<Vec<Post>, DatabaseError> {
|
||||
|
|
|
@ -289,10 +289,10 @@ pub async fn delete_profile(
|
|||
|
||||
pub async fn search_profile(
|
||||
db_client: &impl GenericClient,
|
||||
username: &String,
|
||||
instance: &Option<String>,
|
||||
username: &str,
|
||||
instance: Option<&String>,
|
||||
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||
let db_search_query = match &instance {
|
||||
let db_search_query = match instance {
|
||||
Some(instance) => {
|
||||
// Search for exact profile name.
|
||||
// Fetch from remote server if not found
|
||||
|
|
|
@ -14,7 +14,7 @@ use super::types::{
|
|||
JsonResourceDescriptor,
|
||||
};
|
||||
|
||||
pub async fn get_user_info(
|
||||
async fn get_user_info(
|
||||
db_pool: &Pool,
|
||||
config: &Config,
|
||||
query_params: WebfingerQueryParams,
|
||||
|
@ -53,7 +53,7 @@ pub async fn get_user_info(
|
|||
}
|
||||
|
||||
#[get("/.well-known/webfinger")]
|
||||
async fn get_descriptor(
|
||||
pub async fn get_descriptor(
|
||||
config: web::Data<Config>,
|
||||
db_bool: web::Data<Pool>,
|
||||
query_params: web::Query<WebfingerQueryParams>,
|
||||
|
|
Loading…
Reference in a new issue