Make media URLs in Mastodon API responses relative to current origin

This commit is contained in:
silverpill 2023-02-21 21:39:42 +00:00
parent c796cddff8
commit e1e9851d5c
18 changed files with 204 additions and 33 deletions

View file

@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fixed actor object JSON-LD validation errors. - Fixed actor object JSON-LD validation errors.
- Fixed activity JSON-LD validation errors. - Fixed activity JSON-LD validation errors.
- Make media URLs in Mastodon API responses relative to current origin.
## [1.13.1] - 2023-02-09 ## [1.13.1] - 2023-02-09

View file

@ -14,6 +14,9 @@ pub fn get_hostname(url: &str) -> Result<String, ParseError> {
} }
pub fn guess_protocol(hostname: &str) -> &'static str { pub fn guess_protocol(hostname: &str) -> &'static str {
if hostname == "localhost" {
return "http";
};
let maybe_ipv4_address = hostname.parse::<Ipv4Addr>(); let maybe_ipv4_address = hostname.parse::<Ipv4Addr>();
if let Ok(_ipv4_address) = maybe_ipv4_address { if let Ok(_ipv4_address) = maybe_ipv4_address {
return "http"; return "http";

View file

@ -1,6 +1,6 @@
use actix_web::{ use actix_web::{
body::{BodySize, BoxBody, MessageBody}, body::{BodySize, BoxBody, MessageBody},
dev::ServiceResponse, dev::{ConnectionInfo, ServiceResponse},
error::{Error, JsonPayloadError}, error::{Error, JsonPayloadError},
http::StatusCode, http::StatusCode,
middleware::{ErrorHandlerResponse, ErrorHandlers}, middleware::{ErrorHandlerResponse, ErrorHandlers},
@ -10,6 +10,8 @@ use actix_web::{
}; };
use serde_json::json; use serde_json::json;
use mitra_utils::urls::guess_protocol;
use crate::errors::HttpError; use crate::errors::HttpError;
pub type FormOrJson<T> = Either<Form<T>, Json<T>>; pub type FormOrJson<T> = Either<Form<T>, Json<T>>;
@ -46,3 +48,15 @@ pub fn json_error_handler(
other_error => other_error.into(), other_error => other_error.into(),
} }
} }
pub fn get_request_base_url(connection_info: ConnectionInfo) -> String {
// TODO: HTTP server should set X-Forwarded-Proto header
// let scheme = connection_info.scheme();
let host = connection_info.host();
let scheme = if let Some((hostname, _port)) = host.split_once(':') {
guess_protocol(hostname)
} else {
guess_protocol(host)
};
format!("{}://{}", scheme, host)
}

View file

@ -117,14 +117,15 @@ pub struct Account {
impl Account { impl Account {
pub fn from_profile( pub fn from_profile(
base_url: &str,
instance_url: &str, instance_url: &str,
profile: DbActorProfile, profile: DbActorProfile,
) -> Self { ) -> Self {
let profile_url = profile.actor_url(instance_url); let profile_url = profile.actor_url(instance_url);
let avatar_url = profile.avatar let avatar_url = profile.avatar
.map(|image| get_file_url(instance_url, &image.file_name)); .map(|image| get_file_url(base_url, &image.file_name));
let header_url = profile.banner let header_url = profile.banner
.map(|image| get_file_url(instance_url, &image.file_name)); .map(|image| get_file_url(base_url, &image.file_name));
let is_locked = profile.actor_json let is_locked = profile.actor_json
.map(|actor| actor.manually_approves_followers) .map(|actor| actor.manually_approves_followers)
.unwrap_or(false); .unwrap_or(false);
@ -207,6 +208,7 @@ impl Account {
} }
pub fn from_user( pub fn from_user(
base_url: &str,
instance_url: &str, instance_url: &str,
user: User, user: User,
) -> Self { ) -> Self {
@ -224,6 +226,7 @@ impl Account {
}; };
let role = ApiRole::from_db(user.role); let role = ApiRole::from_db(user.role);
let mut account = Self::from_profile( let mut account = Self::from_profile(
base_url,
instance_url, instance_url,
user.profile, user.profile,
); );
@ -502,10 +505,12 @@ pub struct ApiSubscription {
impl ApiSubscription { impl ApiSubscription {
pub fn from_subscription( pub fn from_subscription(
base_url: &str,
instance_url: &str, instance_url: &str,
subscription: Subscription, subscription: Subscription,
) -> Self { ) -> Self {
let sender = Account::from_profile( let sender = Account::from_profile(
base_url,
instance_url, instance_url,
subscription.sender, subscription.sender,
); );
@ -545,6 +550,7 @@ mod tests {
..Default::default() ..Default::default()
}; };
let account = Account::from_profile( let account = Account::from_profile(
INSTANCE_URL,
INSTANCE_URL, INSTANCE_URL,
profile, profile,
); );
@ -570,6 +576,7 @@ mod tests {
..Default::default() ..Default::default()
}; };
let account = Account::from_user( let account = Account::from_user(
INSTANCE_URL,
INSTANCE_URL, INSTANCE_URL,
user, user,
); );

View file

@ -1,6 +1,12 @@
use actix_web::{ use actix_web::{
get, patch, post, web, dev::ConnectionInfo,
HttpRequest, HttpResponse, Scope, get,
patch,
post,
web,
HttpRequest,
HttpResponse,
Scope,
}; };
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use uuid::Uuid; use uuid::Uuid;
@ -33,6 +39,7 @@ use crate::ethereum::{
gate::is_allowed_user, gate::is_allowed_user,
identity::verify_eip191_identity_proof, identity::verify_eip191_identity_proof,
}; };
use crate::http::get_request_base_url;
use crate::identity::{ use crate::identity::{
claims::create_identity_claim, claims::create_identity_claim,
did::Did, did::Did,
@ -110,6 +117,7 @@ use super::types::{
#[post("")] #[post("")]
pub async fn create_account( pub async fn create_account(
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
maybe_blockchain: web::Data<Option<ContractSet>>, maybe_blockchain: web::Data<Option<ContractSet>>,
@ -195,6 +203,7 @@ pub async fn create_account(
}; };
log::warn!("created user {}", user.id); log::warn!("created user {}", user.id);
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
user, user,
); );
@ -204,12 +213,14 @@ pub async fn create_account(
#[get("/verify_credentials")] #[get("/verify_credentials")]
async fn verify_credentials( async fn verify_credentials(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
) -> Result<HttpResponse, HttpError> { ) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?; let db_client = &**get_database_client(&db_pool).await?;
let user = get_current_user(db_client, auth.token()).await?; let user = get_current_user(db_client, auth.token()).await?;
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
user, user,
); );
@ -219,6 +230,7 @@ async fn verify_credentials(
#[patch("/update_credentials")] #[patch("/update_credentials")]
async fn update_credentials( async fn update_credentials(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
account_data: web::Json<AccountUpdateData>, account_data: web::Json<AccountUpdateData>,
@ -246,6 +258,7 @@ async fn update_credentials(
).await?.enqueue(db_client).await?; ).await?.enqueue(db_client).await?;
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
current_user, current_user,
); );
@ -278,6 +291,7 @@ async fn get_unsigned_update(
#[post("/send_activity")] #[post("/send_activity")]
async fn send_signed_activity( async fn send_signed_activity(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
data: web::Json<SignedActivity>, data: web::Json<SignedActivity>,
@ -323,6 +337,7 @@ async fn send_signed_activity(
outgoing_activity.enqueue(db_client).await?; outgoing_activity.enqueue(db_client).await?;
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
current_user, current_user,
); );
@ -363,6 +378,7 @@ async fn get_identity_claim(
#[post("/identity_proof")] #[post("/identity_proof")]
async fn create_identity_proof( async fn create_identity_proof(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
proof_data: web::Json<IdentityProofData>, proof_data: web::Json<IdentityProofData>,
@ -441,6 +457,7 @@ async fn create_identity_proof(
).await?.enqueue(db_client).await?; ).await?.enqueue(db_client).await?;
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
current_user, current_user,
); );
@ -465,6 +482,7 @@ async fn get_relationships_view(
#[get("/lookup")] #[get("/lookup")]
async fn lookup_acct( async fn lookup_acct(
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
query_params: web::Query<LookupAcctQueryParams>, query_params: web::Query<LookupAcctQueryParams>,
@ -472,6 +490,7 @@ async fn lookup_acct(
let db_client = &**get_database_client(&db_pool).await?; let db_client = &**get_database_client(&db_pool).await?;
let profile = get_profile_by_acct(db_client, &query_params.acct).await?; let profile = get_profile_by_acct(db_client, &query_params.acct).await?;
let account = Account::from_profile( let account = Account::from_profile(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
profile, profile,
); );
@ -480,6 +499,7 @@ async fn lookup_acct(
#[get("/search")] #[get("/search")]
async fn search_by_acct( async fn search_by_acct(
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
query_params: web::Query<SearchAcctQueryParams>, query_params: web::Query<SearchAcctQueryParams>,
@ -490,9 +510,11 @@ async fn search_by_acct(
&query_params.q, &query_params.q,
query_params.limit.inner(), query_params.limit.inner(),
).await?; ).await?;
let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = profiles.into_iter() let accounts: Vec<Account> = profiles.into_iter()
.map(|profile| Account::from_profile( .map(|profile| Account::from_profile(
&base_url,
&instance_url, &instance_url,
profile, profile,
)) ))
@ -502,6 +524,7 @@ async fn search_by_acct(
#[get("/search_did")] #[get("/search_did")]
async fn search_by_did( async fn search_by_did(
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
query_params: web::Query<SearchDidQueryParams>, query_params: web::Query<SearchDidQueryParams>,
@ -510,9 +533,11 @@ async fn search_by_did(
let did: Did = query_params.did.parse() let did: Did = query_params.did.parse()
.map_err(|_| ValidationError("invalid DID"))?; .map_err(|_| ValidationError("invalid DID"))?;
let profiles = search_profiles_by_did(db_client, &did, false).await?; let profiles = search_profiles_by_did(db_client, &did, false).await?;
let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = profiles.into_iter() let accounts: Vec<Account> = profiles.into_iter()
.map(|profile| Account::from_profile( .map(|profile| Account::from_profile(
&base_url,
&instance_url, &instance_url,
profile, profile,
)) ))
@ -522,6 +547,7 @@ async fn search_by_did(
#[get("/{account_id}")] #[get("/{account_id}")]
async fn get_account( async fn get_account(
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
account_id: web::Path<Uuid>, account_id: web::Path<Uuid>,
@ -529,6 +555,7 @@ async fn get_account(
let db_client = &**get_database_client(&db_pool).await?; let db_client = &**get_database_client(&db_pool).await?;
let profile = get_profile_by_id(db_client, &account_id).await?; let profile = get_profile_by_id(db_client, &account_id).await?;
let account = Account::from_profile( let account = Account::from_profile(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
profile, profile,
); );
@ -608,6 +635,7 @@ async fn unfollow_account(
#[get("/{account_id}/statuses")] #[get("/{account_id}/statuses")]
async fn get_account_statuses( async fn get_account_statuses(
auth: Option<BearerAuth>, auth: Option<BearerAuth>,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
account_id: web::Path<Uuid>, account_id: web::Path<Uuid>,
@ -636,6 +664,7 @@ async fn get_account_statuses(
).await?; ).await?;
let statuses = build_status_list( let statuses = build_status_list(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
maybe_current_user.as_ref(), maybe_current_user.as_ref(),
posts, posts,
@ -646,6 +675,7 @@ async fn get_account_statuses(
#[get("/{account_id}/followers")] #[get("/{account_id}/followers")]
async fn get_account_followers( async fn get_account_followers(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
account_id: web::Path<Uuid>, account_id: web::Path<Uuid>,
@ -668,9 +698,11 @@ async fn get_account_followers(
).await?; ).await?;
let max_index = usize::from(query_params.limit.inner().saturating_sub(1)); let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
let maybe_last_id = followers.get(max_index).map(|item| item.relationship_id); let maybe_last_id = followers.get(max_index).map(|item| item.relationship_id);
let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = followers.into_iter() let accounts: Vec<Account> = followers.into_iter()
.map(|item| Account::from_profile( .map(|item| Account::from_profile(
&base_url,
&instance_url, &instance_url,
item.profile, item.profile,
)) ))
@ -687,6 +719,7 @@ async fn get_account_followers(
#[get("/{account_id}/following")] #[get("/{account_id}/following")]
async fn get_account_following( async fn get_account_following(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
account_id: web::Path<Uuid>, account_id: web::Path<Uuid>,
@ -709,9 +742,11 @@ async fn get_account_following(
).await?; ).await?;
let max_index = usize::from(query_params.limit.inner().saturating_sub(1)); let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
let maybe_last_id = following.get(max_index).map(|item| item.relationship_id); let maybe_last_id = following.get(max_index).map(|item| item.relationship_id);
let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = following.into_iter() let accounts: Vec<Account> = following.into_iter()
.map(|item| Account::from_profile( .map(|item| Account::from_profile(
&base_url,
&instance_url, &instance_url,
item.profile, item.profile,
)) ))
@ -728,6 +763,7 @@ async fn get_account_following(
#[get("/{account_id}/subscribers")] #[get("/{account_id}/subscribers")]
async fn get_account_subscribers( async fn get_account_subscribers(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
account_id: web::Path<Uuid>, account_id: web::Path<Uuid>,
@ -741,6 +777,7 @@ async fn get_account_subscribers(
let subscriptions: Vec<ApiSubscription> = vec![]; let subscriptions: Vec<ApiSubscription> = vec![];
return Ok(HttpResponse::Ok().json(subscriptions)); return Ok(HttpResponse::Ok().json(subscriptions));
}; };
let base_url = get_request_base_url(connection_info);
let instance_url = config.instance_url(); let instance_url = config.instance_url();
let subscriptions: Vec<ApiSubscription> = get_incoming_subscriptions( let subscriptions: Vec<ApiSubscription> = get_incoming_subscriptions(
db_client, db_client,
@ -751,6 +788,7 @@ async fn get_account_subscribers(
.await? .await?
.into_iter() .into_iter()
.map(|subscription| ApiSubscription::from_subscription( .map(|subscription| ApiSubscription::from_subscription(
&base_url,
&instance_url, &instance_url,
subscription, subscription,
)) ))

View file

@ -13,8 +13,8 @@ pub struct CustomEmoji {
} }
impl CustomEmoji { impl CustomEmoji {
pub fn from_db(instance_url: &str, emoji: DbEmoji) -> Self { pub fn from_db(base_url: &str, emoji: DbEmoji) -> Self {
let image_url = get_file_url(instance_url, &emoji.image.file_name); let image_url = get_file_url(base_url, &emoji.image.file_name);
Self { Self {
shortcode: emoji.emoji_name, shortcode: emoji.emoji_name,
url: image_url.clone(), url: image_url.clone(),

View file

@ -1,23 +1,28 @@
use actix_web::{get, web, HttpResponse, Scope}; use actix_web::{
dev::ConnectionInfo,
use mitra_config::Config; get,
web,
HttpResponse,
Scope,
};
use crate::database::{get_database_client, DbPool}; use crate::database::{get_database_client, DbPool};
use crate::errors::HttpError; use crate::errors::HttpError;
use crate::http::get_request_base_url;
use crate::models::emojis::queries::get_local_emojis; use crate::models::emojis::queries::get_local_emojis;
use super::types::CustomEmoji; use super::types::CustomEmoji;
/// https://docs.joinmastodon.org/methods/custom_emojis/ /// https://docs.joinmastodon.org/methods/custom_emojis/
#[get("")] #[get("")]
async fn custom_emoji_list( async fn custom_emoji_list(
config: web::Data<Config>, connection_info: ConnectionInfo,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
) -> Result<HttpResponse, HttpError> { ) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?; let db_client = &**get_database_client(&db_pool).await?;
let instance = config.instance(); let base_url = get_request_base_url(connection_info);
let emojis: Vec<CustomEmoji> = get_local_emojis(db_client).await? let emojis: Vec<CustomEmoji> = get_local_emojis(db_client).await?
.into_iter() .into_iter()
.map(|db_emoji| CustomEmoji::from_db(&instance.url(), db_emoji)) .map(|db_emoji| CustomEmoji::from_db(&base_url, db_emoji))
.collect(); .collect();
Ok(HttpResponse::Ok().json(emojis)) Ok(HttpResponse::Ok().json(emojis))
} }

View file

@ -1,11 +1,18 @@
/// https://docs.joinmastodon.org/methods/instance/directory/ /// https://docs.joinmastodon.org/methods/instance/directory/
use actix_web::{get, web, HttpResponse, Scope}; use actix_web::{
dev::ConnectionInfo,
get,
web,
HttpResponse,
Scope,
};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use mitra_config::Config; use mitra_config::Config;
use crate::database::{get_database_client, DbPool}; use crate::database::{get_database_client, DbPool};
use crate::errors::HttpError; use crate::errors::HttpError;
use crate::http::get_request_base_url;
use crate::mastodon_api::{ use crate::mastodon_api::{
accounts::types::Account, accounts::types::Account,
oauth::auth::get_current_user, oauth::auth::get_current_user,
@ -16,6 +23,7 @@ use super::types::DirectoryQueryParams;
#[get("")] #[get("")]
async fn profile_directory( async fn profile_directory(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
query_params: web::Query<DirectoryQueryParams>, query_params: web::Query<DirectoryQueryParams>,
@ -28,10 +36,12 @@ async fn profile_directory(
query_params.offset, query_params.offset,
query_params.limit.inner(), query_params.limit.inner(),
).await?; ).await?;
let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = profiles let accounts: Vec<Account> = profiles
.into_iter() .into_iter()
.map(|profile| Account::from_profile( .map(|profile| Account::from_profile(
&base_url,
&instance_url, &instance_url,
profile, profile,
)) ))

View file

@ -26,7 +26,7 @@ pub struct Attachment {
} }
impl Attachment { impl Attachment {
pub fn from_db(instance_url: &str, db_attachment: DbMediaAttachment) -> Self { pub fn from_db(base_url: &str, db_attachment: DbMediaAttachment) -> Self {
let attachment_type = let attachment_type =
AttachmentType::from_media_type(db_attachment.media_type); AttachmentType::from_media_type(db_attachment.media_type);
let attachment_type_mastodon = match attachment_type { let attachment_type_mastodon = match attachment_type {
@ -35,7 +35,7 @@ impl Attachment {
AttachmentType::Video => "video", AttachmentType::Video => "video",
}; };
let attachment_url = get_file_url( let attachment_url = get_file_url(
instance_url, base_url,
&db_attachment.file_name, &db_attachment.file_name,
); );
Self { Self {

View file

@ -35,15 +35,17 @@ pub struct ApiNotification {
impl ApiNotification { impl ApiNotification {
pub fn from_db( pub fn from_db(
base_url: &str,
instance_url: &str, instance_url: &str,
notification: Notification, notification: Notification,
) -> Self { ) -> Self {
let account = Account::from_profile( let account = Account::from_profile(
base_url,
instance_url, instance_url,
notification.sender, notification.sender,
); );
let status = notification.post.map(|post| { let status = notification.post.map(|post| {
Status::from_post(instance_url, post) Status::from_post(base_url, instance_url, post)
}); });
let event_type_mastodon = match notification.event_type { let event_type_mastodon = match notification.event_type {
EventType::Follow => "follow", EventType::Follow => "follow",

View file

@ -1,6 +1,8 @@
/// https://docs.joinmastodon.org/methods/notifications/ /// https://docs.joinmastodon.org/methods/notifications/
use actix_web::{ use actix_web::{
get, web, dev::ConnectionInfo,
get,
web,
HttpRequest, HttpResponse, HttpRequest, HttpResponse,
Scope as ActixScope, Scope as ActixScope,
}; };
@ -10,6 +12,7 @@ use mitra_config::Config;
use crate::database::{get_database_client, DbPool}; use crate::database::{get_database_client, DbPool};
use crate::errors::HttpError; use crate::errors::HttpError;
use crate::http::get_request_base_url;
use crate::mastodon_api::{ use crate::mastodon_api::{
oauth::auth::get_current_user, oauth::auth::get_current_user,
pagination::get_paginated_response, pagination::get_paginated_response,
@ -20,6 +23,7 @@ use super::types::{ApiNotification, NotificationQueryParams};
#[get("")] #[get("")]
async fn get_notifications_view( async fn get_notifications_view(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
query_params: web::Query<NotificationQueryParams>, query_params: web::Query<NotificationQueryParams>,
@ -27,6 +31,8 @@ async fn get_notifications_view(
) -> Result<HttpResponse, HttpError> { ) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?; let db_client = &**get_database_client(&db_pool).await?;
let current_user = get_current_user(db_client, auth.token()).await?; let current_user = get_current_user(db_client, auth.token()).await?;
let base_url = get_request_base_url(connection_info);
let instance = config.instance();
let notifications: Vec<ApiNotification> = get_notifications( let notifications: Vec<ApiNotification> = get_notifications(
db_client, db_client,
&current_user.id, &current_user.id,
@ -34,13 +40,17 @@ async fn get_notifications_view(
query_params.limit.inner(), query_params.limit.inner(),
).await? ).await?
.into_iter() .into_iter()
.map(|item| ApiNotification::from_db(&config.instance_url(), item)) .map(|item| ApiNotification::from_db(
&base_url,
&instance.url(),
item,
))
.collect(); .collect();
let max_index = usize::from(query_params.limit.inner().saturating_sub(1)); let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
let maybe_last_id = notifications.get(max_index) let maybe_last_id = notifications.get(max_index)
.map(|item| item.id.clone()); .map(|item| item.id.clone());
let response = get_paginated_response( let response = get_paginated_response(
&config.instance_url(), &instance.url(),
request.uri().path(), request.uri().path(),
notifications, notifications,
maybe_last_id, maybe_last_id,

View file

@ -1,11 +1,18 @@
/// https://docs.joinmastodon.org/methods/search/ /// https://docs.joinmastodon.org/methods/search/
use actix_web::{get, web, HttpResponse, Scope}; use actix_web::{
dev::ConnectionInfo,
get,
web,
HttpResponse,
Scope,
};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use mitra_config::Config; use mitra_config::Config;
use crate::database::{get_database_client, DbPool}; use crate::database::{get_database_client, DbPool};
use crate::errors::HttpError; use crate::errors::HttpError;
use crate::http::get_request_base_url;
use crate::mastodon_api::{ use crate::mastodon_api::{
accounts::types::Account, accounts::types::Account,
oauth::auth::get_current_user, oauth::auth::get_current_user,
@ -18,6 +25,7 @@ use super::types::{SearchQueryParams, SearchResults};
#[get("")] #[get("")]
async fn search_view( async fn search_view(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
query_params: web::Query<SearchQueryParams>, query_params: web::Query<SearchQueryParams>,
@ -31,15 +39,18 @@ async fn search_view(
query_params.q.trim(), query_params.q.trim(),
query_params.limit.inner(), query_params.limit.inner(),
).await?; ).await?;
let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = profiles.into_iter() let accounts: Vec<Account> = profiles.into_iter()
.map(|profile| Account::from_profile( .map(|profile| Account::from_profile(
&base_url,
&instance_url, &instance_url,
profile, profile,
)) ))
.collect(); .collect();
let statuses = build_status_list( let statuses = build_status_list(
db_client, db_client,
&base_url,
&instance_url, &instance_url,
Some(&current_user), Some(&current_user),
posts, posts,

View file

@ -1,4 +1,11 @@
use actix_web::{get, post, web, HttpResponse, Scope}; use actix_web::{
dev::ConnectionInfo,
get,
post,
web,
HttpResponse,
Scope,
};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use mitra_config::Config; use mitra_config::Config;
@ -12,6 +19,7 @@ use crate::activitypub::{
}; };
use crate::database::{get_database_client, DatabaseError, DbPool}; use crate::database::{get_database_client, DatabaseError, DbPool};
use crate::errors::{HttpError, ValidationError}; use crate::errors::{HttpError, ValidationError};
use crate::http::get_request_base_url;
use crate::mastodon_api::{ use crate::mastodon_api::{
accounts::types::Account, accounts::types::Account,
oauth::auth::get_current_user, oauth::auth::get_current_user,
@ -37,6 +45,7 @@ use super::types::{
#[post("/change_password")] #[post("/change_password")]
async fn change_password_view( async fn change_password_view(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
request_data: web::Json<PasswordChangeRequest>, request_data: web::Json<PasswordChangeRequest>,
@ -47,6 +56,7 @@ async fn change_password_view(
.map_err(|_| HttpError::InternalError)?; .map_err(|_| HttpError::InternalError)?;
set_user_password(db_client, &current_user.id, password_hash).await?; set_user_password(db_client, &current_user.id, password_hash).await?;
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
current_user, current_user,
); );
@ -117,6 +127,7 @@ async fn import_follows_view(
#[post("/move_followers")] #[post("/move_followers")]
async fn move_followers( async fn move_followers(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
request_data: web::Json<MoveFollowersRequest>, request_data: web::Json<MoveFollowersRequest>,
@ -194,6 +205,7 @@ async fn move_followers(
).enqueue(db_client).await?; ).enqueue(db_client).await?;
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info),
&instance.url(), &instance.url(),
current_user, current_user,
); );

View file

@ -75,6 +75,7 @@ pub async fn parse_microsyntaxes(
/// Load related objects and build status for API response /// Load related objects and build status for API response
pub async fn build_status( pub async fn build_status(
db_client: &impl DatabaseClient, db_client: &impl DatabaseClient,
base_url: &str,
instance_url: &str, instance_url: &str,
user: Option<&User>, user: Option<&User>,
mut post: Post, mut post: Post,
@ -83,12 +84,13 @@ pub async fn build_status(
if let Some(user) = user { if let Some(user) = user {
add_user_actions(db_client, &user.id, vec![&mut post]).await?; add_user_actions(db_client, &user.id, vec![&mut post]).await?;
}; };
let status = Status::from_post(instance_url, post); let status = Status::from_post(base_url, instance_url, post);
Ok(status) Ok(status)
} }
pub async fn build_status_list( pub async fn build_status_list(
db_client: &impl DatabaseClient, db_client: &impl DatabaseClient,
base_url: &str,
instance_url: &str, instance_url: &str,
user: Option<&User>, user: Option<&User>,
mut posts: Vec<Post>, mut posts: Vec<Post>,
@ -99,7 +101,7 @@ pub async fn build_status_list(
}; };
let statuses: Vec<Status> = posts let statuses: Vec<Status> = posts
.into_iter() .into_iter()
.map(|post| Status::from_post(instance_url, post)) .map(|post| Status::from_post(base_url, instance_url, post))
.collect(); .collect();
Ok(statuses) Ok(statuses)
} }

View file

@ -85,12 +85,13 @@ pub struct Status {
impl Status { impl Status {
pub fn from_post( pub fn from_post(
base_url: &str,
instance_url: &str, instance_url: &str,
post: Post, post: Post,
) -> Self { ) -> Self {
let object_id = post.object_id(instance_url); let object_id = post.object_id(instance_url);
let attachments: Vec<Attachment> = post.attachments.into_iter() let attachments: Vec<Attachment> = post.attachments.into_iter()
.map(|item| Attachment::from_db(instance_url, item)) .map(|item| Attachment::from_db(base_url, item))
.collect(); .collect();
let mentions: Vec<Mention> = post.mentions.into_iter() let mentions: Vec<Mention> = post.mentions.into_iter()
.map(|item| Mention::from_profile(instance_url, item)) .map(|item| Mention::from_profile(instance_url, item))
@ -99,20 +100,21 @@ impl Status {
.map(|tag_name| Tag::from_tag_name(instance_url, tag_name)) .map(|tag_name| Tag::from_tag_name(instance_url, tag_name))
.collect(); .collect();
let emojis: Vec<CustomEmoji> = post.emojis.into_iter() let emojis: Vec<CustomEmoji> = post.emojis.into_iter()
.map(|emoji| CustomEmoji::from_db(instance_url, emoji)) .map(|emoji| CustomEmoji::from_db(base_url, emoji))
.collect(); .collect();
let account = Account::from_profile( let account = Account::from_profile(
base_url,
instance_url, instance_url,
post.author, post.author,
); );
let reblog = if let Some(repost_of) = post.repost_of { let reblog = if let Some(repost_of) = post.repost_of {
let status = Status::from_post(instance_url, *repost_of); let status = Status::from_post(base_url, instance_url, *repost_of);
Some(Box::new(status)) Some(Box::new(status))
} else { } else {
None None
}; };
let links = post.linked.into_iter().map(|post| { let links = post.linked.into_iter().map(|post| {
Status::from_post(instance_url, post) Status::from_post(base_url, instance_url, post)
}).collect(); }).collect();
let visibility = match post.visibility { let visibility = match post.visibility {
Visibility::Public => "public", Visibility::Public => "public",

View file

@ -1,5 +1,13 @@
/// https://docs.joinmastodon.org/methods/statuses/ /// https://docs.joinmastodon.org/methods/statuses/
use actix_web::{delete, get, post, web, HttpResponse, Scope}; use actix_web::{
delete,
dev::ConnectionInfo,
get,
post,
web,
HttpResponse,
Scope,
};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use chrono::Utc; use chrono::Utc;
use uuid::Uuid; use uuid::Uuid;
@ -21,7 +29,7 @@ use crate::activitypub::builders::{
use crate::database::{get_database_client, DatabaseError, DbPool}; use crate::database::{get_database_client, DatabaseError, DbPool};
use crate::errors::{HttpError, ValidationError}; use crate::errors::{HttpError, ValidationError};
use crate::ethereum::nft::create_mint_signature; use crate::ethereum::nft::create_mint_signature;
use crate::http::FormOrJson; use crate::http::{get_request_base_url, FormOrJson};
use crate::ipfs::{ use crate::ipfs::{
store as ipfs_store, store as ipfs_store,
posts::PostMetadata, posts::PostMetadata,
@ -69,6 +77,7 @@ use super::types::{
#[post("")] #[post("")]
async fn create_status( async fn create_status(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_data: FormOrJson<StatusData>, status_data: FormOrJson<StatusData>,
@ -190,7 +199,11 @@ async fn create_status(
prepare_create_note(db_client, &instance, &current_user, &post) prepare_create_note(db_client, &instance, &current_user, &post)
.await?.enqueue(db_client).await?; .await?.enqueue(db_client).await?;
let status = Status::from_post(&instance.url(), post); let status = Status::from_post(
&get_request_base_url(connection_info),
&instance.url(),
post,
);
Ok(HttpResponse::Ok().json(status)) Ok(HttpResponse::Ok().json(status))
} }
@ -232,6 +245,7 @@ async fn preview_status(
#[get("/{status_id}")] #[get("/{status_id}")]
async fn get_status( async fn get_status(
auth: Option<BearerAuth>, auth: Option<BearerAuth>,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
@ -247,6 +261,7 @@ async fn get_status(
}; };
let status = build_status( let status = build_status(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
maybe_current_user.as_ref(), maybe_current_user.as_ref(),
post, post,
@ -285,6 +300,7 @@ async fn delete_status(
#[get("/{status_id}/context")] #[get("/{status_id}/context")]
async fn get_context( async fn get_context(
auth: Option<BearerAuth>, auth: Option<BearerAuth>,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
@ -301,6 +317,7 @@ async fn get_context(
).await?; ).await?;
let statuses = build_status_list( let statuses = build_status_list(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
maybe_current_user.as_ref(), maybe_current_user.as_ref(),
posts, posts,
@ -327,6 +344,7 @@ async fn get_context(
async fn get_thread_view( async fn get_thread_view(
auth: Option<BearerAuth>, auth: Option<BearerAuth>,
config: web::Data<Config>, config: web::Data<Config>,
connection_info: ConnectionInfo,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
) -> Result<HttpResponse, HttpError> { ) -> Result<HttpResponse, HttpError> {
@ -342,6 +360,7 @@ async fn get_thread_view(
).await?; ).await?;
let statuses = build_status_list( let statuses = build_status_list(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
maybe_current_user.as_ref(), maybe_current_user.as_ref(),
posts, posts,
@ -352,6 +371,7 @@ async fn get_thread_view(
#[post("/{status_id}/favourite")] #[post("/{status_id}/favourite")]
async fn favourite( async fn favourite(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
@ -386,6 +406,7 @@ async fn favourite(
let status = build_status( let status = build_status(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
Some(&current_user), Some(&current_user),
post, post,
@ -396,6 +417,7 @@ async fn favourite(
#[post("/{status_id}/unfavourite")] #[post("/{status_id}/unfavourite")]
async fn unfavourite( async fn unfavourite(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
@ -427,6 +449,7 @@ async fn unfavourite(
let status = build_status( let status = build_status(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
Some(&current_user), Some(&current_user),
post, post,
@ -437,6 +460,7 @@ async fn unfavourite(
#[post("/{status_id}/reblog")] #[post("/{status_id}/reblog")]
async fn reblog( async fn reblog(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
@ -465,6 +489,7 @@ async fn reblog(
let status = build_status( let status = build_status(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
Some(&current_user), Some(&current_user),
repost, repost,
@ -475,6 +500,7 @@ async fn reblog(
#[post("/{status_id}/unreblog")] #[post("/{status_id}/unreblog")]
async fn unreblog( async fn unreblog(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
@ -502,6 +528,7 @@ async fn unreblog(
let status = build_status( let status = build_status(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
Some(&current_user), Some(&current_user),
post, post,
@ -512,6 +539,7 @@ async fn unreblog(
#[post("/{status_id}/make_permanent")] #[post("/{status_id}/make_permanent")]
async fn make_permanent( async fn make_permanent(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
@ -561,6 +589,7 @@ async fn make_permanent(
let status = build_status( let status = build_status(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
Some(&current_user), Some(&current_user),
post, post,
@ -605,6 +634,7 @@ async fn get_signature(
#[post("/{status_id}/token_minted")] #[post("/{status_id}/token_minted")]
async fn token_minted( async fn token_minted(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
status_id: web::Path<Uuid>, status_id: web::Path<Uuid>,
@ -625,6 +655,7 @@ async fn token_minted(
let status = build_status( let status = build_status(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
Some(&current_user), Some(&current_user),
post, post,

View file

@ -1,4 +1,11 @@
use actix_web::{get, post, web, HttpResponse, Scope}; use actix_web::{
dev::ConnectionInfo,
get,
post,
web,
HttpResponse,
Scope,
};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use uuid::Uuid; use uuid::Uuid;
@ -15,6 +22,7 @@ use crate::ethereum::{
is_registered_recipient, is_registered_recipient,
}, },
}; };
use crate::http::get_request_base_url;
use crate::mastodon_api::{ use crate::mastodon_api::{
accounts::types::Account, accounts::types::Account,
oauth::auth::get_current_user, oauth::auth::get_current_user,
@ -92,6 +100,7 @@ async fn get_subscription_options(
#[post("/options")] #[post("/options")]
pub async fn register_subscription_option( pub async fn register_subscription_option(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
maybe_blockchain: web::Data<Option<ContractSet>>, maybe_blockchain: web::Data<Option<ContractSet>>,
@ -166,6 +175,7 @@ pub async fn register_subscription_option(
}; };
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
current_user, current_user,
); );

View file

@ -1,11 +1,18 @@
/// https://docs.joinmastodon.org/methods/timelines/ /// https://docs.joinmastodon.org/methods/timelines/
use actix_web::{get, web, HttpResponse, Scope}; use actix_web::{
dev::ConnectionInfo,
get,
web,
HttpResponse,
Scope,
};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use mitra_config::Config; use mitra_config::Config;
use crate::database::{get_database_client, DbPool}; use crate::database::{get_database_client, DbPool};
use crate::errors::HttpError; use crate::errors::HttpError;
use crate::http::get_request_base_url;
use crate::mastodon_api::{ use crate::mastodon_api::{
oauth::auth::get_current_user, oauth::auth::get_current_user,
statuses::helpers::build_status_list, statuses::helpers::build_status_list,
@ -20,6 +27,7 @@ use super::types::TimelineQueryParams;
#[get("/home")] #[get("/home")]
async fn home_timeline( async fn home_timeline(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
query_params: web::Query<TimelineQueryParams>, query_params: web::Query<TimelineQueryParams>,
@ -34,6 +42,7 @@ async fn home_timeline(
).await?; ).await?;
let statuses = build_status_list( let statuses = build_status_list(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
Some(&current_user), Some(&current_user),
posts, posts,
@ -45,6 +54,7 @@ async fn home_timeline(
#[get("/public")] #[get("/public")]
async fn public_timeline( async fn public_timeline(
auth: BearerAuth, auth: BearerAuth,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
query_params: web::Query<TimelineQueryParams>, query_params: web::Query<TimelineQueryParams>,
@ -59,6 +69,7 @@ async fn public_timeline(
).await?; ).await?;
let statuses = build_status_list( let statuses = build_status_list(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
Some(&current_user), Some(&current_user),
posts, posts,
@ -69,6 +80,7 @@ async fn public_timeline(
#[get("/tag/{hashtag}")] #[get("/tag/{hashtag}")]
async fn hashtag_timeline( async fn hashtag_timeline(
auth: Option<BearerAuth>, auth: Option<BearerAuth>,
connection_info: ConnectionInfo,
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
hashtag: web::Path<String>, hashtag: web::Path<String>,
@ -88,6 +100,7 @@ async fn hashtag_timeline(
).await?; ).await?;
let statuses = build_status_list( let statuses = build_status_list(
db_client, db_client,
&get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
maybe_current_user.as_ref(), maybe_current_user.as_ref(),
posts, posts,