voice clone requests endpoints

This commit is contained in:
Brandon Thomas committed 2022-02-19 13:48:36 -05:00
1 parent 9280ec11f7
commit e6f0908a3d
8 files changed
+363 -34

No files matched your search

@@ -25,6 +25,10 @@ CREATE TABLE voice_clone_requests(
-- ========== REASON ==========
-- Visibility
is_for_private_use BOOLEAN NOT NULL DEFAULT FALSE,
is_for_public_use BOOLEAN NOT NULL DEFAULT FALSE,
-- How they'll use
is_for_studio BOOLEAN NOT NULL DEFAULT FALSE,
is_for_twitch_tts BOOLEAN NOT NULL DEFAULT FALSE,
@@ -34,13 +38,10 @@ CREATE TABLE voice_clone_requests(
is_for_other BOOLEAN NOT NULL DEFAULT FALSE,
optional_notes_on_use MEDIUMTEXT DEFAULT NULL,
-- Visibility
is_for_private_use BOOLEAN NOT NULL DEFAULT FALSE,
is_for_public_use BOOLEAN NOT NULL DEFAULT FALSE,
-- Ownership
-- Subject/Ownership
is_own_voice BOOLEAN NOT NULL DEFAULT FALSE,
is_third_party_voice BOOLEAN NOT NULL DEFAULT FALSE,
optional_notes_on_subject VARCHAR(255) DEFAULT NULL,
-- ========== RECORDING QUALITY / EQUIPMENT ==========
@@ -33,6 +33,10 @@ pub struct VoiceCloneRequest {
pub email_address: String,
pub discord_username: String,
// Visibility
pub is_for_private_use: bool,
pub is_for_public_use: bool,
// Use
pub is_for_studio: bool,
pub is_for_twitch_tts: bool,
@@ -42,13 +46,10 @@ pub struct VoiceCloneRequest {
pub is_for_other: bool,
pub optional_notes_on_use: Option<String>,
// Visibility
pub is_for_private_use: bool,
pub is_for_public_use: bool,
// Ownership
// Subject/Ownership
pub is_own_voice: bool,
pub is_third_party_voice: bool,
pub optional_notes_on_subject: Option<String>,
// Equipment
pub has_clean_audio_recordings: bool,
@@ -76,6 +77,9 @@ SELECT
email_address,
discord_username,
is_for_private_use,
is_for_public_use,
is_for_studio,
is_for_twitch_tts,
is_for_api_use,
@@ -84,11 +88,9 @@ SELECT
is_for_other,
optional_notes_on_use,
is_for_private_use,
is_for_public_use,
is_own_voice,
is_third_party_voice,
optional_notes_on_subject,
has_clean_audio_recordings,
has_good_microphone,
@@ -124,6 +126,9 @@ SELECT
email_address,
discord_username,
is_for_private_use,
is_for_public_use,
is_for_studio,
is_for_twitch_tts,
is_for_api_use,
@@ -132,11 +137,9 @@ SELECT
is_for_other,
optional_notes_on_use,
is_for_private_use,
is_for_public_use,
is_own_voice,
is_third_party_voice,
optional_notes_on_subject,
has_clean_audio_recordings,
has_good_microphone,
@@ -172,6 +175,8 @@ fn handle_result(
maybe_user_token: record.maybe_user_token,
email_address: record.email_address,
discord_username: record.discord_username,
is_for_private_use: i8_to_bool(record.is_for_private_use),
is_for_public_use: i8_to_bool(record.is_for_public_use),
is_for_studio: i8_to_bool(record.is_for_studio),
is_for_twitch_tts: i8_to_bool(record.is_for_twitch_tts),
is_for_api_use: i8_to_bool(record.is_for_api_use),
@@ -179,10 +184,9 @@ fn handle_result(
is_for_games: i8_to_bool(record.is_for_games),
is_for_other: i8_to_bool(record.is_for_other),
optional_notes_on_use: record.optional_notes_on_use,
is_for_private_use: i8_to_bool(record.is_for_private_use),
is_for_public_use: i8_to_bool(record.is_for_public_use),
is_own_voice: i8_to_bool(record.is_own_voice),
is_third_party_voice: i8_to_bool(record.is_third_party_voice),
optional_notes_on_subject: record.optional_notes_on_subject,
has_clean_audio_recordings: i8_to_bool(record.has_clean_audio_recordings),
has_good_microphone: i8_to_bool(record.has_good_microphone),
optional_questions: record.optional_questions,
@@ -201,6 +205,10 @@ struct VoiceCloneRequestInternal {
pub email_address: String,
pub discord_username: String,
// Visibility
pub is_for_private_use: i8,
pub is_for_public_use: i8,
// Use
pub is_for_studio: i8,
pub is_for_twitch_tts: i8,
@@ -210,13 +218,10 @@ struct VoiceCloneRequestInternal {
pub is_for_other: i8,
pub optional_notes_on_use: Option<String>,
// Visibility
pub is_for_private_use: i8,
pub is_for_public_use: i8,
// Ownership
// Subject/Ownership
pub is_own_voice: i8,
pub is_third_party_voice: i8,
pub optional_notes_on_subject: Option<String>,
// Equipment
pub has_clean_audio_recordings: i8,
@@ -12,6 +12,10 @@ pub struct InsertVoiceCloneRequestBuilder {
pub email_address: String,
pub discord_username: String,
// Visibility
pub is_for_private_use: bool,
pub is_for_public_use: bool,
// Use
pub is_for_studio: bool,
pub is_for_twitch_tts: bool,
@@ -21,13 +25,10 @@ pub struct InsertVoiceCloneRequestBuilder {
pub is_for_other: bool,
pub optional_notes_on_use: Option<String>,
// Visibility
pub is_for_private_use: bool,
pub is_for_public_use: bool,
// Ownership
// Subject/Ownership
pub is_own_voice: bool,
pub is_third_party_voice: bool,
pub optional_notes_on_subject: Option<String>,
// Equipment
pub has_clean_audio_recordings: bool,
@@ -58,6 +59,9 @@ SET
email_address = ?,
discord_username = ?,
is_for_private_use = ?,
is_for_public_use = ?,
is_for_studio = ?,
is_for_twitch_tts = ?,
is_for_api_use = ?,
@@ -66,11 +70,9 @@ SET
is_for_other = ?,
optional_notes_on_use = ?,
is_for_private_use = ?,
is_for_public_use = ?,
is_own_voice = ?,
is_third_party_voice = ?,
optional_notes_on_subject = ?,
has_clean_audio_recordings = ?,
has_good_microphone = ?,
@@ -87,6 +89,9 @@ SET
&self.email_address,
&self.discord_username,
&self.is_for_private_use,
&self.is_for_public_use,
&self.is_for_studio,
&self.is_for_twitch_tts,
&self.is_for_api_use,
@@ -95,11 +100,9 @@ SET
&self.is_for_other,
&self.optional_notes_on_use,
&self.is_for_private_use,
&self.is_for_public_use,
&self.is_own_voice,
&self.is_third_party_voice,
&self.optional_notes_on_subject,
&self.has_clean_audio_recordings,
&self.has_good_microphone,
@@ -7,4 +7,5 @@ pub mod moderation;
pub mod tts;
pub mod twitch;
pub mod users;
pub mod voice_clone_requests;
pub mod w2l;
@@ -0,0 +1,130 @@
use actix_http::Error;
use actix_http::http::header;
use actix_web::HttpResponseBuilder;
use actix_web::cookie::Cookie;
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::{Path, Json};
use actix_web::{Responder, web, HttpResponse, error, HttpRequest};
use crate::http_server::web_utils::ip_address::get_request_ip;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::http_server::web_utils::response_success_helpers::simple_json_success;
use crate::server_state::ServerState;
use database_queries::queries::voice_clone_requests::get_voice_clone_request::{get_voice_clone_request_by_user_token, get_voice_clone_request_by_token};
use database_queries::queries::voice_clone_requests::insert_voice_clone_requests_builder::InsertVoiceCloneRequestBuilder;
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
use log::{info, warn, log};
use regex::Regex;
use sqlx::MySqlPool;
use sqlx::error::DatabaseError;
use sqlx::error::Error::Database;
use sqlx::mysql::MySqlDatabaseError;
use std::fmt;
use std::sync::Arc;
// =============== Request ===============
#[derive(Deserialize)]
pub struct CheckIfVoiceRequestSubmittedRequest {
/// Though we look up logged in users by user token, we can also look up non-logged-in users
/// with a token lookup. We can store this in frontend state or a cookie.
pub maybe_request_token: Option<String>,
}
// =============== Success Response ===============
#[derive(Serialize)]
pub struct CheckIfVoiceRequestSubmittedResponse {
pub success: bool,
pub has_submitted: bool,
}
// =============== Error Response ===============
#[derive(Debug, Serialize)]
pub enum CheckIfVoiceRequestSubmittedError {
BadInput(String),
NotAuthorized,
ServerError,
}
impl ResponseError for CheckIfVoiceRequestSubmittedError {
fn status_code(&self) -> StatusCode {
match *self {
CheckIfVoiceRequestSubmittedError::BadInput(_) => StatusCode::BAD_REQUEST,
CheckIfVoiceRequestSubmittedError::NotAuthorized => StatusCode::UNAUTHORIZED,
CheckIfVoiceRequestSubmittedError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
serialize_as_json_error(self)
}
}
// NB: Not using DeriveMore since Clion doesn't understand it.
impl fmt::Display for CheckIfVoiceRequestSubmittedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
// =============== Handler ===============
pub async fn check_if_voice_clone_request_submitted_handler(
http_request: HttpRequest,
request: web::Json<CheckIfVoiceRequestSubmittedRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, CheckIfVoiceRequestSubmittedError>
{
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
CheckIfVoiceRequestSubmittedError::ServerError
})?;
let mut submitted_by_token = false;
let mut submitted_by_user = false;
// Non-logged in accounts (via cookie or frontend mechanism)
if let Some(token) = request.maybe_request_token.as_deref() {
let request = get_voice_clone_request_by_token(
token, &server_state.mysql_pool)
.await
.map_err(|e| {
warn!("Database error: {:?}", e);
CheckIfVoiceRequestSubmittedError::ServerError
})?;
submitted_by_token = request.is_some();
}
// Logged in users
if let Some(user) = maybe_user_session.as_ref() {
let request = get_voice_clone_request_by_user_token(
&user.user_token, &server_state.mysql_pool)
.await
.map_err(|e| {
warn!("Database error: {:?}", e);
CheckIfVoiceRequestSubmittedError::ServerError
})?;
submitted_by_user = request.is_some();
}
let has_submitted = submitted_by_token || submitted_by_user;
let response = CheckIfVoiceRequestSubmittedResponse {
success: true,
has_submitted,
};
let body = serde_json::to_string(&response)
.map_err(|e| CheckIfVoiceRequestSubmittedError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
@@ -0,0 +1,158 @@
use actix_http::Error;
use actix_http::http::header;
use actix_web::HttpResponseBuilder;
use actix_web::cookie::Cookie;
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::{Path, Json};
use actix_web::{Responder, web, HttpResponse, error, HttpRequest};
use crate::http_server::web_utils::ip_address::get_request_ip;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::http_server::web_utils::response_success_helpers::simple_json_success;
use crate::server_state::ServerState;
use database_queries::queries::voice_clone_requests::insert_voice_clone_requests_builder::InsertVoiceCloneRequestBuilder;
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
use log::{info, warn, log};
use regex::Regex;
use sqlx::MySqlPool;
use sqlx::error::DatabaseError;
use sqlx::error::Error::Database;
use sqlx::mysql::MySqlDatabaseError;
use std::fmt;
use std::sync::Arc;
// =============== Request ===============
#[derive(Deserialize)]
pub struct CreateVoiceRequestRequest {
pub idempotency_token: String,
// Contact
pub email_address: String,
pub discord_username: String,
// TODO: Make enum.
// Visibility
pub is_for_private_use: bool,
pub is_for_public_use: bool,
// Use
pub is_for_studio: bool,
pub is_for_twitch_tts: bool,
pub is_for_api_use: bool,
pub is_for_music: bool,
pub is_for_games: bool,
pub is_for_other: bool,
pub optional_notes_on_use: Option<String>,
// Subject/Ownership
pub is_own_voice: bool,
pub is_third_party_voice: bool,
pub optional_notes_on_subject: Option<String>,
// Equipment
pub has_clean_audio_recordings: bool,
pub has_good_microphone: bool,
// Comments
pub optional_questions: Option<String>,
pub optional_extra_comments: Option<String>,
}
// =============== Success Response ===============
#[derive(Serialize)]
pub struct CreateVoiceRequestResponse {
pub success: bool,
}
// =============== Error Response ===============
#[derive(Debug, Serialize)]
pub enum CreateVoiceRequestError {
BadInput(String),
NotAuthorized,
ServerError,
}
impl ResponseError for CreateVoiceRequestError {
fn status_code(&self) -> StatusCode {
match *self {
CreateVoiceRequestError::BadInput(_) => StatusCode::BAD_REQUEST,
CreateVoiceRequestError::NotAuthorized => StatusCode::UNAUTHORIZED,
CreateVoiceRequestError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
serialize_as_json_error(self)
}
}
// NB: Not using DeriveMore since Clion doesn't understand it.
impl fmt::Display for CreateVoiceRequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
// =============== Handler ===============
pub async fn create_voice_clone_request_handler(
http_request: HttpRequest,
request: web::Json<CreateVoiceRequestRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, CreateVoiceRequestError>
{
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
CreateVoiceRequestError::ServerError
})?;
let creator_ip_address = get_request_ip(&http_request);
let builder = InsertVoiceCloneRequestBuilder {
uuid_idempotency_token: request.idempotency_token.clone(),
maybe_user_token: maybe_user_session.map(|user| user.user_token.clone()),
email_address: request.email_address.clone(),
discord_username: request.discord_username.clone(),
is_for_private_use: request.is_for_private_use,
is_for_public_use: request.is_for_public_use,
is_for_studio: request.is_for_studio,
is_for_twitch_tts: request.is_for_twitch_tts,
is_for_api_use: request.is_for_api_use,
is_for_music: request.is_for_music,
is_for_games: request.is_for_games,
is_for_other: request.is_for_other,
optional_notes_on_use: request.optional_notes_on_use.clone(),
is_own_voice: request.is_own_voice,
is_third_party_voice: request.is_third_party_voice,
optional_notes_on_subject: request.optional_notes_on_subject.clone(),
has_clean_audio_recordings: request.has_clean_audio_recordings,
has_good_microphone: request.has_good_microphone,
optional_questions: request.optional_questions.clone(),
optional_extra_comments: request.optional_extra_comments.clone(),
ip_address_creation: creator_ip_address,
};
builder.insert(&server_state.mysql_pool)
.await
.map_err(|e| {
warn!("creation error: {:?}", e);
CreateVoiceRequestError::ServerError
})?;
let response = CreateVoiceRequestResponse {
success: true,
};
let body = serde_json::to_string(&response)
.map_err(|e| CreateVoiceRequestError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
@@ -0,0 +1,2 @@
pub mod create_voice_clone_request;
pub mod check_if_voice_clone_request_submitted;
@@ -79,6 +79,8 @@ use crate::http_server::endpoints::twitch::event_rules::create_event_rule::creat
use crate::http_server::endpoints::twitch::event_rules::list_event_rules_for_user::list_twitch_event_rules_for_user_handler;
use crate::http_server::endpoints::twitch::event_rules::delete_event_rule::delete_twitch_event_rule_handler;
use crate::http_server::endpoints::twitch::event_rules::edit_event_rule::edit_twitch_event_rule_handler;
use crate::http_server::endpoints::voice_clone_requests::create_voice_clone_request::create_voice_clone_request_handler;
use crate::http_server::endpoints::voice_clone_requests::check_if_voice_clone_request_submitted::check_if_voice_clone_request_submitted_handler;
pub fn add_routes<T, B> (app: App<T, B>) -> App<T, B>
where
@@ -97,6 +99,7 @@ pub fn add_routes<T, B> (app: App<T, B>) -> App<T, B>
app = add_category_routes(app); /* /category */
app = add_user_profile_routes(app); /* /user */
app = add_api_token_routes(app); /* /api_tokens */
app = add_voice_clone_request_routes(app); /* /voice_clone_requests */
app = add_twitch_oauth_routes(app); /* /twitch */ // TODO: MAYBE TEMPORARY
// ==================== ACCOUNT CREATION / SESSION MANAGEMENT ====================
@@ -571,6 +574,32 @@ fn add_api_token_routes<T, B> (app: App<T, B>) -> App<T, B>
)
}
// ==================== API TOKEN ROUTES ====================
fn add_voice_clone_request_routes<T, B> (app: App<T, B>) -> App<T, B>
where
B: MessageBody,
T: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
app.service(web::scope("/voice_clone_requests")
.service(web::resource("/create")
.route(web::post().to(create_voice_clone_request_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(web::resource("/check")
.route(web::post().to(check_if_voice_clone_request_submitted_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
)
}
// ==================== TWITCH ROUTES ====================
// TODO: Maybe move these into an "oauth-gateway" type http service.