diff --git a/docs/openapi.yaml b/docs/openapi.yaml index d4bbaeb..d6774bd 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -633,6 +633,36 @@ paths: type: array items: $ref: '#/components/schemas/Notification' + /api/v1/settings/export_followers: + get: + summary: Export followers to CSV file + security: + - tokenAuth: [] + responses: + 200: + description: Successful operation + content: + text/csv: + schema: + type: string + example: | + user1@example.org + user2@example.org + /api/v1/settings/export_follows: + get: + summary: Export follows to CSV file + security: + - tokenAuth: [] + responses: + 200: + description: Successful operation + content: + text/csv: + schema: + type: string + example: | + user1@example.org + user2@example.org /api/v1/statuses: post: summary: Create new post. diff --git a/src/main.rs b/src/main.rs index 4c6bce3..5a767f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ 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_api_scope; +use mitra::mastodon_api::settings::views::settings_api_scope; use mitra::mastodon_api::statuses::views::status_api_scope; use mitra::mastodon_api::subscriptions::views::subscription_api_scope; use mitra::mastodon_api::timelines::views::timeline_api_scope; @@ -146,6 +147,7 @@ async fn main() -> std::io::Result<()> { .service(media_api_scope()) .service(notification_api_scope()) .service(search_api_scope()) + .service(settings_api_scope()) .service(status_api_scope()) .service(subscription_api_scope()) .service(timeline_api_scope()) diff --git a/src/mastodon_api/mod.rs b/src/mastodon_api/mod.rs index 6105850..127c9b4 100644 --- a/src/mastodon_api/mod.rs +++ b/src/mastodon_api/mod.rs @@ -7,6 +7,7 @@ pub mod notifications; pub mod oauth; mod pagination; pub mod search; +pub mod settings; pub mod statuses; pub mod subscriptions; pub mod timelines; diff --git a/src/mastodon_api/settings/helpers.rs b/src/mastodon_api/settings/helpers.rs new file mode 100644 index 0000000..786f1c6 --- /dev/null +++ b/src/mastodon_api/settings/helpers.rs @@ -0,0 +1,63 @@ +use tokio_postgres::GenericClient; +use uuid::Uuid; + +use crate::errors::DatabaseError; +use crate::models::profiles::types::DbActorProfile; +use crate::models::relationships::queries::{get_followers, get_following}; + +fn export_profiles_to_csv( + local_hostname: &str, + profiles: Vec, +) -> String { + let mut csv = String::new(); + for profile in profiles { + let actor_address = profile.actor_address(local_hostname); + csv += &format!("{}\n", actor_address); + }; + csv +} + +pub async fn export_followers( + db_client: &impl GenericClient, + local_hostname: &str, + user_id: &Uuid, +) -> Result { + let followers = get_followers(db_client, user_id).await?; + let csv = export_profiles_to_csv(local_hostname, followers); + Ok(csv) +} + +pub async fn export_follows( + db_client: &impl GenericClient, + local_hostname: &str, + user_id: &Uuid, +) -> Result { + let following = get_following(db_client, user_id).await?; + let csv = export_profiles_to_csv(local_hostname, following); + Ok(csv) +} + +#[cfg(test)] +mod tests { + use crate::activitypub::actors::types::Actor; + use super::*; + + #[test] + fn test_export_profiles_to_csv() { + let profile_1 = DbActorProfile { + username: "user1".to_string(), + ..Default::default() + }; + let profile_2 = DbActorProfile { + username: "user2".to_string(), + hostname: Some("test.net".to_string()), + actor_json: Some(Actor::default()), + ..Default::default() + }; + let csv = export_profiles_to_csv( + "example.org", + vec![profile_1, profile_2], + ); + assert_eq!(csv, "user1@example.org\nuser2@test.net\n"); + } +} diff --git a/src/mastodon_api/settings/mod.rs b/src/mastodon_api/settings/mod.rs new file mode 100644 index 0000000..5957542 --- /dev/null +++ b/src/mastodon_api/settings/mod.rs @@ -0,0 +1,2 @@ +mod helpers; +pub mod views; diff --git a/src/mastodon_api/settings/views.rs b/src/mastodon_api/settings/views.rs new file mode 100644 index 0000000..eb6029f --- /dev/null +++ b/src/mastodon_api/settings/views.rs @@ -0,0 +1,52 @@ +use actix_web::{get, web, HttpResponse, Scope}; +use actix_web_httpauth::extractors::bearer::BearerAuth; + +use crate::config::Config; +use crate::database::{Pool, get_database_client}; +use crate::errors::HttpError; +use crate::mastodon_api::oauth::auth::get_current_user; +use super::helpers::{export_followers, export_follows}; + +#[get("/export_followers")] +async fn export_followers_view( + auth: BearerAuth, + config: web::Data, + db_pool: web::Data, +) -> Result { + let db_client = &**get_database_client(&db_pool).await?; + let current_user = get_current_user(db_client, auth.token()).await?; + let csv = export_followers( + db_client, + &config.instance().hostname(), + ¤t_user.id, + ).await?; + let response = HttpResponse::Ok() + .content_type("text/csv") + .body(csv); + Ok(response) +} + +#[get("/export_follows")] +async fn export_follows_view( + auth: BearerAuth, + config: web::Data, + db_pool: web::Data, +) -> Result { + let db_client = &**get_database_client(&db_pool).await?; + let current_user = get_current_user(db_client, auth.token()).await?; + let csv = export_follows( + db_client, + &config.instance().hostname(), + ¤t_user.id, + ).await?; + let response = HttpResponse::Ok() + .content_type("text/csv") + .body(csv); + Ok(response) +} + +pub fn settings_api_scope() -> Scope { + web::scope("/api/v1/settings") + .service(export_followers_view) + .service(export_follows_view) +}