From 44d0efdd8455190a260721d60663da4e34cd5fa3 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 19 Jun 2026 19:56:12 -0400 Subject: [PATCH] new endpoint in rust --- .../artcraft_api_defs/src/video_info/mod.rs | 1 + .../artcraft_api_defs/src/video_info/notes.rs | 51 ++++++++ .../web/storyteller_web/src/docs/api_doc.rs | 9 ++ .../http_server/endpoints/video_info/mod.rs | 1 + .../video_info/video_info_notes_handler.rs | 110 ++++++++++++++++++ .../application_routes/video_info_routes.rs | 5 + 6 files changed, 177 insertions(+) create mode 100644 crates/api_clients/artcraft/artcraft_api_defs/src/video_info/notes.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/video_info/video_info_notes_handler.rs diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/video_info/mod.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/video_info/mod.rs index 65044ee75a..af59a00150 100644 --- a/crates/api_clients/artcraft/artcraft_api_defs/src/video_info/mod.rs +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/video_info/mod.rs @@ -1,2 +1,3 @@ +pub mod notes; pub mod read_only; pub mod upload; diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/video_info/notes.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/video_info/notes.rs new file mode 100644 index 0000000000..68d3d6262b --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/video_info/notes.rs @@ -0,0 +1,51 @@ +//! Request/response types for `POST /v1/video_info/notes`. +//! +//! A note is user-submitted context about an uploaded video (what model they +//! believe it is, where it came from, free-form comments). Always tied to an +//! [`UploadedVideoToken`]; pass a [`UploadVideoNoteToken`] to update an existing +//! note instead of creating a new one. + +use serde_derive::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use enums::by_table::uploaded_video_notes::uploaded_video_note_reported_model_type::UploadedVideoNoteReportedModelType; +use enums::by_table::uploaded_video_notes::uploaded_video_note_reported_website::UploadedVideoNoteReportedWebsite; +use tokens::tokens::uploaded_video_notes::UploadVideoNoteToken; +use tokens::tokens::uploaded_videos::UploadedVideoToken; + +/// Request body for `POST /v1/video_info/notes`. +#[derive(Deserialize, ToSchema, Debug, Clone)] +pub struct VideoInfoNoteRequest { + /// The uploaded video this note is about. Required. + pub uploaded_video_token: UploadedVideoToken, + + /// If present, update this existing note instead of creating a new one. + pub maybe_uploaded_video_note_token: Option, + + /// Original filename of the submitted clip. + pub maybe_filename: Option, + + /// The model type the submitter claims this video is (dropdown / enum). + pub maybe_reported_model_type: Option, + + /// Free-form custom model name (trimmed; empty becomes absent). + pub maybe_reported_model_name: Option, + + /// The website / platform the video came from (dropdown / enum). + pub maybe_website: Option, + + /// Free-form website (trimmed; empty becomes absent). + pub maybe_other_website: Option, + + /// Free-form comments (trimmed; empty becomes absent). + pub maybe_comments: Option, +} + +/// Response body for `POST /v1/video_info/notes`. +#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)] +pub struct VideoInfoNoteResponse { + pub success: bool, + + /// The created or updated note's token (e.g. `uvnote_…`). + pub uploaded_video_note_token: UploadVideoNoteToken, +} diff --git a/crates/service/web/storyteller_web/src/docs/api_doc.rs b/crates/service/web/storyteller_web/src/docs/api_doc.rs index 2695998198..fdd08f7799 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -366,8 +366,12 @@ use artcraft_api_defs::user_referral_codes::list_referral_codes::*; use artcraft_api_defs::web_referrals::log_web_referral::*; use artcraft_api_defs::video_info::read_only::*; use artcraft_api_defs::video_info::upload::*; +use artcraft_api_defs::video_info::notes::*; +use enums::by_table::uploaded_video_notes::uploaded_video_note_reported_model_type::UploadedVideoNoteReportedModelType; +use enums::by_table::uploaded_video_notes::uploaded_video_note_reported_website::UploadedVideoNoteReportedWebsite; use crate::http_server::endpoints::video_info::video_info_read_info_handler::*; use crate::http_server::endpoints::video_info::video_info_upload_handler::*; +use crate::http_server::endpoints::video_info::video_info_notes_handler::*; use crate::http_server::endpoints::web_referrals::log_web_referral_handler::*; use crate::http_server::endpoints::image_studio::update_gpt_image_job_status_handler::*; use crate::http_server::endpoints::voice_conversion::enqueue_seed_vc_inference_handler::*; @@ -623,6 +627,7 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux // Web Referrals crate::http_server::endpoints::video_info::video_info_read_info_handler::video_info_read_info_handler, crate::http_server::endpoints::video_info::video_info_upload_handler::video_info_upload_handler, + crate::http_server::endpoints::video_info::video_info_notes_handler::video_info_notes_handler, crate::http_server::endpoints::web_referrals::log_web_referral_handler::log_web_referral_handler, // User Referral Codes crate::http_server::endpoints::user_referral_codes::create_referral_code_handler::create_referral_code_handler, @@ -1260,6 +1265,10 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux VideoInfoReadOnlyResponse, VideoInfoUploadForm, VideoInfoUploadResponse, + VideoInfoNoteRequest, + VideoInfoNoteResponse, + UploadedVideoNoteReportedModelType, + UploadedVideoNoteReportedWebsite, VideoProvenanceKind, SeedanceVideoInfo, VeoVideoInfo, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/video_info/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/video_info/mod.rs index 1c32ec8859..6d2a09d51f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/video_info/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/video_info/mod.rs @@ -1,3 +1,4 @@ pub mod provenance; +pub mod video_info_notes_handler; pub mod video_info_read_info_handler; pub mod video_info_upload_handler; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/video_info/video_info_notes_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/video_info/video_info_notes_handler.rs new file mode 100644 index 0000000000..f21b32da06 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/video_info/video_info_notes_handler.rs @@ -0,0 +1,110 @@ +//! `POST /v1/video_info/notes` — attach (or update) a user-submitted note about +//! an uploaded video. +//! +//! Always tied to an `UploadedVideoToken`. If the request carries an +//! `UploadVideoNoteToken`, the existing note is updated; otherwise a new note is +//! created. The IP address is stamped on the create or update column accordingly. + +use std::marker::PhantomData; +use std::sync::Arc; + +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; + +use artcraft_api_defs::video_info::notes::{VideoInfoNoteRequest, VideoInfoNoteResponse}; +use http_server_common::request::get_request_ip::get_request_ip; +use mysql_queries::queries::uploaded_video_notes::insert_uploaded_video_note::{ + insert_uploaded_video_note, InsertUploadedVideoNoteArgs, +}; +use mysql_queries::queries::uploaded_video_notes::update_uploaded_video_note::{ + update_uploaded_video_note, UpdateUploadedVideoNoteArgs, +}; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::web_utils::redis_rate_limiter::RateLimiterError; +use crate::state::server_state::ServerState; + +/// Create or update a note about an uploaded video. +#[utoipa::path( + post, + tag = "Video Info", + path = "/v1/video_info/notes", + request_body = VideoInfoNoteRequest, + responses( + (status = 200, description = "Note created or updated", body = VideoInfoNoteResponse), + (status = 400, description = "Bad input", body = CommonWebError), + (status = 500, description = "Server error", body = CommonWebError), + ) +)] +pub async fn video_info_notes_handler( + http_request: HttpRequest, + server_state: web::Data>, + Json(request): Json, +) -> Result, CommonWebError> { + // IP-based rate limit (shared with the other video-info endpoints). Fails open. + match server_state.redis_rate_limiters.video_info_read_only.rate_limit_request(&http_request).await { + Ok(()) => {} + Err(RateLimiterError::RateLimitExceededError) => return Err(CommonWebError::TooManyRequests), + Err(RateLimiterError::ClientError) => {} // fail open + } + + let ip_address = get_request_ip(&http_request); + + // Free-form text fields are trimmed; empty becomes absent. + let maybe_filename = request.maybe_filename.as_deref(); + let maybe_reported_model_name = normalize_optional(&request.maybe_reported_model_name); + let maybe_other_website = normalize_optional(&request.maybe_other_website); + let maybe_comments = normalize_optional(&request.maybe_comments); + + let mut mysql_connection = server_state.mysql_pool.acquire().await?; + + let uploaded_video_note_token = match &request.maybe_uploaded_video_note_token { + // Update an existing note. + Some(note_token) => { + update_uploaded_video_note(UpdateUploadedVideoNoteArgs { + token: note_token, + maybe_filename, + maybe_reported_model_type: request.maybe_reported_model_type, + maybe_reported_model_name: maybe_reported_model_name.as_deref(), + maybe_website: request.maybe_website, + maybe_other_website: maybe_other_website.as_deref(), + maybe_comments: maybe_comments.as_deref(), + maybe_comment_update_ip_address: Some(ip_address.as_str()), + mysql_executor: &mut *mysql_connection, + phantom: PhantomData, + }) + .await?; + note_token.clone() + } + // Create a new note. + None => { + insert_uploaded_video_note(InsertUploadedVideoNoteArgs { + uploaded_video_token: &request.uploaded_video_token, + maybe_filename, + maybe_reported_model_type: request.maybe_reported_model_type, + maybe_reported_model_name: maybe_reported_model_name.as_deref(), + maybe_website: request.maybe_website, + maybe_other_website: maybe_other_website.as_deref(), + maybe_comments: maybe_comments.as_deref(), + comment_create_ip_address: &ip_address, + mysql_executor: &mut *mysql_connection, + phantom: PhantomData, + }) + .await? + } + }; + + Ok(Json(VideoInfoNoteResponse { + success: true, + uploaded_video_note_token, + })) +} + +/// Trim a free-form text field; a missing or whitespace-only value becomes `None`. +fn normalize_optional(value: &Option) -> Option { + value + .as_deref() + .map(str::trim) + .filter(|trimmed| !trimmed.is_empty()) + .map(str::to_string) +} diff --git a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/video_info_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/video_info_routes.rs index 8b61ed7888..2924847c95 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/video_info_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/video_info_routes.rs @@ -4,6 +4,7 @@ use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::error::Error; use actix_web::{web, App, HttpResponse}; +use crate::http_server::endpoints::video_info::video_info_notes_handler::video_info_notes_handler; use crate::http_server::endpoints::video_info::video_info_read_info_handler::video_info_read_info_handler; use crate::http_server::endpoints::video_info::video_info_upload_handler::video_info_upload_handler; @@ -27,5 +28,9 @@ where .route(web::post().to(video_info_upload_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) + .service(web::resource("/notes") + .route(web::post().to(video_info_notes_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) ) }