From c3e41a1a3e4d614660b2edf5871d9f80eae2b52b Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 3 Jun 2025 10:26:47 -0400 Subject: [PATCH] fal webhook endpoint --- .../src/response/response_success_helpers.rs | 7 ++ .../by_table/media_files/media_file_type.rs | 5 ++ .../config/storyteller-web.development.env | 3 +- .../image/remove_image_background_handler.rs | 4 +- .../src/http_server/endpoints/mod.rs | 1 + .../endpoints/webhooks/fal_webhook_handler.rs | 74 +++++++++++++++++++ .../src/http_server/endpoints/webhooks/mod.rs | 1 + .../src/http_server/routes/add_routes.rs | 2 + .../http_server/routes/add_webhook_routes.rs | 24 ++++++ .../src/http_server/routes/mod.rs | 1 + 10 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/fal_webhook_handler.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/routes/add_webhook_routes.rs diff --git a/crates/lib/deprecated/http_server_common/src/response/response_success_helpers.rs b/crates/lib/deprecated/http_server_common/src/response/response_success_helpers.rs index dae959059f..e2009a67b8 100644 --- a/crates/lib/deprecated/http_server_common/src/response/response_success_helpers.rs +++ b/crates/lib/deprecated/http_server_common/src/response/response_success_helpers.rs @@ -1,10 +1,17 @@ use actix_web::HttpResponse; +use actix_web::web::Json; #[derive(Serialize)] pub struct SimpleGenericJsonSuccess { pub success: bool, } +impl SimpleGenericJsonSuccess { + pub fn wrapped(success: bool) -> Json { + Json(SimpleGenericJsonSuccess { success }) + } +} + pub fn simple_json_success() -> HttpResponse { let response = SimpleGenericJsonSuccess { success: true, diff --git a/crates/schema/public/enums/src/by_table/media_files/media_file_type.rs b/crates/schema/public/enums/src/by_table/media_files/media_file_type.rs index e3c0d13901..4f8ee74a3a 100644 --- a/crates/schema/public/enums/src/by_table/media_files/media_file_type.rs +++ b/crates/schema/public/enums/src/by_table/media_files/media_file_type.rs @@ -97,6 +97,11 @@ impl MediaFileType { pub fn is_jpg_or_png(&self) -> bool { matches!(self, Self::Jpg | Self::Png) } + + /// A little bit less strict than `is_jpg_or_png`, this includes the legacy `Image` type. + pub fn is_jpg_or_png_or_legacy_image(&self) -> bool { + matches!(self, Self::Jpg | Self::Png | Self::Image) + } pub fn to_str(&self) -> &'static str { match self { diff --git a/crates/service/web/storyteller_web/config/storyteller-web.development.env b/crates/service/web/storyteller_web/config/storyteller-web.development.env index 135d69958e..d8893840e6 100644 --- a/crates/service/web/storyteller_web/config/storyteller-web.development.env +++ b/crates/service/web/storyteller_web/config/storyteller-web.development.env @@ -42,5 +42,4 @@ GC_ENABLED_PUBLIC_BUCKET_NAME="REPLACE_ME" AUDIO_UPLOADS_BUCKET_ROOT=/user_uploaded_w2l_audio # Fake API key for FAL -FAL_API_KEY="REPLACE_ME" -FAL_WEBHOOK_URL="https://api.storyteller.ai/v1/fal/webhook" +FAL_WEBHOOK_URL="https://macaroni-1.tailce84f.ts.net/v1/webhooks/fal" diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/remove_image_background_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/remove_image_background_handler.rs index b7b6e188d5..02815442ca 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/remove_image_background_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/remove_image_background_handler.rs @@ -63,7 +63,7 @@ impl fmt::Display for RemoveImageBackgroundError { // =============== Handler =============== -/// Change (or remove) the "title" of a media file. +/// Background removal #[utoipa::path( post, tag = "Generate Images", @@ -140,7 +140,7 @@ pub async fn remove_image_background_handler( } }; - if !media_file.media_type.is_jpg_or_png() { + if !media_file.media_type.is_jpg_or_png_or_legacy_image() { return Err(RemoveImageBackgroundError::BadInput("Media file must be a JPG or PNG image".to_string())); } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/mod.rs index ad98ad8040..ae99ed27df 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/mod.rs @@ -25,4 +25,5 @@ pub mod users; pub mod voice_clone_requests; pub mod voice_conversion; pub mod voice_designer; +pub mod webhooks; pub mod weights; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/fal_webhook_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/fal_webhook_handler.rs new file mode 100644 index 0000000000..730aa1529c --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/fal_webhook_handler.rs @@ -0,0 +1,74 @@ +use std::fmt; +use std::sync::Arc; + +use crate::state::server_state::ServerState; +use actix_web::error::ResponseError; +use actix_web::http::StatusCode; +use actix_web::web::Json; +use actix_web::{web, HttpRequest, HttpResponse}; +use http_server_common::response::response_success_helpers::SimpleGenericJsonSuccess; +use http_server_common::response::serialize_as_json_error::serialize_as_json_error; +use utoipa::ToSchema; + +// =============== Error Response =============== + +#[derive(Debug, Serialize, ToSchema)] +pub enum FalWebhookError { + BadInput(String), + NotFound, + NotAuthorized, + ServerError, +} + +impl ResponseError for FalWebhookError { + fn status_code(&self) -> StatusCode { + match *self { + FalWebhookError::BadInput(_) => StatusCode::BAD_REQUEST, + FalWebhookError::NotFound => StatusCode::NOT_FOUND, + FalWebhookError::NotAuthorized => StatusCode::UNAUTHORIZED, + FalWebhookError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, + } + } + + fn error_response(&self) -> HttpResponse { + serialize_as_json_error(self) + } +} + +// NB: Not using derive_more::Display since Clion doesn't understand it. +impl fmt::Display for FalWebhookError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +// =============== Handler =============== + +/// Fal webhook +#[utoipa::path( + post, + tag = "Webooks", + path = "/v1/webhooks/fal", + responses( + (status = 200, description = "Success", body = SimpleGenericJsonSuccess), + (status = 400, description = "Bad input", body = FalWebhookError), + (status = 401, description = "Not authorized", body = FalWebhookError), + (status = 500, description = "Server error", body = FalWebhookError), + ), + params( + ("request" = RemoveImageBackgroundRequest, description = "Payload for Request"), + ) +)] +pub async fn fal_webhook_handler( + http_request: HttpRequest, + server_state: web::Data>, + request_body: web::Bytes +) -> Result, FalWebhookError> { + + let body = String::from_utf8(request_body.to_vec()) + .map_err(|e| FalWebhookError::BadInput(format!("Invalid UTF-8 in request body: {}", e)))?; + + println!("Received Fal webhook with body: {}", body); + + Ok(SimpleGenericJsonSuccess::wrapped(true)) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/mod.rs new file mode 100644 index 0000000000..179ec0cae4 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/webhooks/mod.rs @@ -0,0 +1 @@ +pub mod fal_webhook_handler; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/routes/add_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/add_routes.rs index 6bbeb834a2..242a80dac4 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/add_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/add_routes.rs @@ -127,6 +127,7 @@ use crate::http_server::routes::add_control_plane_routes::add_control_plane_rout use crate::http_server::routes::add_generate_routes::add_generate_routes; use crate::http_server::routes::add_image_studio_routes::add_image_studio_routes; use crate::http_server::routes::add_studio_gen2_routes::add_studio_gen2_routes; +use crate::http_server::routes::add_webhook_routes::add_webhook_routes; use crate::http_server::routes::beta_key_routes::add_beta_key_routes; use crate::http_server::routes::job_routes::add_job_routes; use crate::http_server::routes::media_files_routes::add_media_file_routes; @@ -179,6 +180,7 @@ pub fn add_routes (app: App, server_environment: ServerEnvironment) -> app = add_engine_routes(app); /* /v1/engine/... */ app = add_control_plane_routes(app); /* /v1/control_plane/... */ app = add_generate_routes(app); /* /v1/generate/... */ + app = add_webhook_routes(app); /* /v1/webhooks/... */ // app = add_image_gen_routes(app); diff --git a/crates/service/web/storyteller_web/src/http_server/routes/add_webhook_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/add_webhook_routes.rs new file mode 100644 index 0000000000..1e07afc636 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/routes/add_webhook_routes.rs @@ -0,0 +1,24 @@ +use crate::http_server::endpoints::webhooks::fal_webhook_handler::fal_webhook_handler; +use actix_http::body::MessageBody; +use actix_service::ServiceFactory; +use actix_web::dev::{ServiceRequest, ServiceResponse}; +use actix_web::{web, App, Error, HttpResponse}; + +pub fn add_webhook_routes (app: App) -> App +where + B: MessageBody, + T: ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + InitError = (), + >, +{ + app.service(web::scope("/v1/webhooks") + .service(web::resource("/fal") + .route(web::post().to(fal_webhook_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + ) +} diff --git a/crates/service/web/storyteller_web/src/http_server/routes/mod.rs b/crates/service/web/storyteller_web/src/http_server/routes/mod.rs index 34e943cbac..2fe4516636 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/mod.rs @@ -2,6 +2,7 @@ mod add_control_plane_routes; mod add_generate_routes; mod add_image_studio_routes; mod add_studio_gen2_routes; +mod add_webhook_routes; mod beta_key_routes; mod job_routes; mod media_files_routes;