add endpoint for editing tts models (untested)

This commit is contained in:
Brandon Thomas committed 2021-06-10 00:29:38 -04:00
1 parent ec1e280eda
commit 3065f20f9f
5 files changed
+414 -1

No files matched your search

+1
View File
@@ -4,6 +4,7 @@ pub mod list_tts_inference_results;
pub mod list_tts_models;
pub mod list_w2l_inference_results;
pub mod list_w2l_templates;
pub mod query_tts_model;
pub mod query_user_profile;
pub mod query_w2l_template;
pub mod sessions;
+171
View File
@@ -0,0 +1,171 @@
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use crate::AnyhowResult;
use crate::database_helpers::boolean_converters::i8_to_bool;
use crate::database_helpers::boolean_converters::nullable_i8_to_optional_bool;
use derive_more::{Display, 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::sync::Arc;
#[derive(Serialize)]
pub struct TtsModelRecordForResponse {
pub model_token: String,
pub tts_model_type: String,
pub creator_user_token: String,
pub creator_username: String,
pub creator_display_name: String,
pub updatable_slug: String,
pub title: String,
pub description_markdown: String,
pub description_rendered_html: String,
pub is_mod_disabled: bool,
pub is_locked_from_user_modification: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub user_deleted_at: Option<DateTime<Utc>>,
pub mod_deleted_at: Option<DateTime<Utc>>,
}
#[derive(Serialize)]
pub struct TtsModelRecordRaw {
pub model_token: String,
pub tts_model_type: String,
pub creator_user_token: String,
pub creator_username: String,
pub creator_display_name: String,
pub updatable_slug: String,
pub title: String,
pub description_markdown: String,
pub description_rendered_html: String,
pub is_mod_disabled: i8,
pub is_locked_from_user_modification: i8,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub user_deleted_at: Option<DateTime<Utc>>,
pub mod_deleted_at: Option<DateTime<Utc>>,
}
pub async fn select_tts_model_by_token(
tts_model_token: &str,
can_see_deleted: bool,
mysql_pool: &MySqlPool
) -> AnyhowResult<Option<TtsModelRecordForResponse>> {
let maybe_record = if can_see_deleted {
select_including_deleted(tts_model_token, mysql_pool).await
} else {
select_without_deleted(tts_model_token, mysql_pool).await
};
let model : TtsModelRecordRaw = match maybe_record {
Ok(model) => model,
Err(ref err) => {
match err {
RowNotFound => {
warn!("tts model not found: {:?}", &err);
return Ok(None);
},
_ => {
warn!("tts model query error: {:?}", &err);
return Err(anyhow!("database error"));
}
}
}
};
let model_for_response = TtsModelRecordForResponse {
model_token: model.model_token.clone(),
tts_model_type: model.tts_model_type.clone(),
creator_user_token: model.creator_user_token.clone(),
creator_username: model.creator_username.clone(),
creator_display_name: model.creator_display_name.clone(),
updatable_slug: model.updatable_slug.clone(),
title: model.title.clone(),
description_markdown: model.description_markdown.clone(),
description_rendered_html: model.description_rendered_html.clone(),
is_mod_disabled: i8_to_bool(model.is_mod_disabled),
is_locked_from_user_modification: i8_to_bool(model.is_locked_from_user_modification),
created_at: model.created_at.clone(),
updated_at: model.updated_at.clone(),
user_deleted_at: model.user_deleted_at.clone(),
mod_deleted_at: model.mod_deleted_at.clone(),
};
Ok(Some(model_for_response))
}
async fn select_including_deleted(
tts_model_token: &str,
mysql_pool: &MySqlPool
) -> Result<TtsModelRecordRaw, sqlx::Error> {
sqlx::query_as!(
TtsModelRecordRaw,
r#"
SELECT
tts.token as model_token,
tts.tts_model_type,
tts.creator_user_token,
users.username as creator_username,
users.display_name as creator_display_name,
tts.updatable_slug,
tts.title,
tts.description_markdown,
tts.description_rendered_html,
tts.is_mod_disabled,
tts.is_locked_from_user_modification,
tts.created_at,
tts.updated_at,
tts.user_deleted_at,
tts.mod_deleted_at
FROM tts_models as tts
JOIN users
ON users.token = tts.creator_user_token
WHERE tts.token = ?
"#,
tts_model_token
)
.fetch_one(mysql_pool)
.await // TODO: This will return error if it doesn't exist
}
async fn select_without_deleted(
tts_model_token: &str,
mysql_pool: &MySqlPool
) -> Result<TtsModelRecordRaw, sqlx::Error> {
sqlx::query_as!(
TtsModelRecordRaw,
r#"
SELECT
tts.token as model_token,
tts.tts_model_type,
tts.creator_user_token,
users.username as creator_username,
users.display_name as creator_display_name,
tts.updatable_slug,
tts.title,
tts.description_markdown,
tts.description_rendered_html,
tts.is_mod_disabled,
tts.is_locked_from_user_modification,
tts.created_at,
tts.updated_at,
tts.user_deleted_at,
tts.mod_deleted_at
FROM tts_models as tts
JOIN users
ON users.token = tts.creator_user_token
WHERE
tts.token = ?
AND tts.user_deleted_at IS NULL
AND tts.mod_deleted_at IS NULL
"#,
tts_model_token
)
.fetch_one(mysql_pool)
.await // TODO: This will return error if it doesn't exist
}
@@ -0,0 +1,234 @@
use actix_http::Error;
use actix_http::http::header;
use actix_web::cookie::Cookie;
use actix_web::dev::HttpResponseBuilder;
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::Path;
use actix_web::{Responder, web, HttpResponse, error, HttpRequest};
use crate::common_queries::query_tts_model::select_tts_model_by_token;
use crate::common_queries::sessions::create_session_for_user;
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 crate::util::email_to_gravatar::email_to_gravatar;
use crate::util::markdown_to_html::markdown_to_html;
use crate::util::random_crockford_token::random_crockford_token;
use crate::util::random_prefix_crockford_token::random_prefix_crockford_token;
use crate::validations::check_for_slurs::contains_slurs;
use derive_more::{Display, Error};
use log::{info, warn, log};
use regex::Regex;
use sqlx::error::DatabaseError;
use sqlx::error::Error::Database;
use sqlx::mysql::MySqlDatabaseError;
use std::sync::Arc;
/// For the URL PathInfo
#[derive(Deserialize)]
pub struct EditTtsModelPathInfo {
model_token: String,
}
#[derive(Deserialize)]
pub struct EditTtsModelRequest {
pub title: Option<String>,
pub description_markdown: Option<String>,
//pub updatable_slug: Option<String>,
//pub creator_set_visibility: Option<String>,
//pub tts_model_type: Option<String>,
//pub text_preprocessing_algorithm: Option<String>,
//pub vocoder_token: Option<String>,
//pub is_mod_disabled: Option<bool>,
//pub is_locked_from_user_modification: Option<bool>,
//pub maybe_mod_comments: Option<String>,
}
#[derive(Debug, Display)]
pub enum EditTtsModelError {
BadInput(String),
NotAuthorized,
TemplateNotFound,
ServerError,
}
impl ResponseError for EditTtsModelError {
fn status_code(&self) -> StatusCode {
match *self {
EditTtsModelError::BadInput(_) => StatusCode::BAD_REQUEST,
EditTtsModelError::NotAuthorized => StatusCode::UNAUTHORIZED,
EditTtsModelError::TemplateNotFound => StatusCode::NOT_FOUND,
EditTtsModelError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let error_reason = match self {
EditTtsModelError::BadInput(reason) => reason.to_string(),
EditTtsModelError::NotAuthorized=> "unauthorized".to_string(),
EditTtsModelError::TemplateNotFound => "not found".to_string(),
EditTtsModelError::ServerError => "server error".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
}
}
pub async fn edit_tts_model_handler(
http_request: HttpRequest,
path: Path<EditTtsModelPathInfo>,
request: web::Json<EditTtsModelRequest>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, EditTtsModelError>
{
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);
EditTtsModelError::ServerError
})?;
let user_session = match maybe_user_session {
Some(session) => session,
None => {
warn!("not logged in");
return Err(EditTtsModelError::NotAuthorized);
}
};
// NB: First permission check.
// Only mods should see deleted models (both user_* and mod_* deleted).
let is_mod_that_can_see_deleted = user_session.can_delete_other_users_tts_models;
let model_lookup_result = select_tts_model_by_token(
&path.model_token,
is_mod_that_can_see_deleted,
&server_state.mysql_pool).await;
let model_record = match model_lookup_result {
Ok(Some(result)) => {
info!("Found model: {}", result.model_token);
result
},
Ok(None) => {
warn!("could not find model");
return Err(EditTtsModelError::TemplateNotFound);
},
Err(err) => {
warn!("error looking up model: {:?}", err);
return Err(EditTtsModelError::TemplateNotFound);
},
};
// NB: Second set of permission checks
let mut editor_is_original_user = false;
let mut editor_is_moderator = false;
if model_record.creator_user_token == user_session.user_token {
editor_is_original_user = true;
}
if user_session.can_edit_other_users_tts_models {
editor_is_moderator = true;
}
if !editor_is_original_user && !editor_is_moderator {
return Err(EditTtsModelError::NotAuthorized);
}
if !editor_is_moderator {
if model_record.is_locked_from_user_modification || model_record.is_mod_disabled {
return Err(EditTtsModelError::NotAuthorized);
}
}
// Fields to set
let mut title = None;
let mut description_markdown = None;
let mut description_html = None;
if let Some(payload) = request.title.as_deref() {
if contains_slurs(payload) {
return Err(EditTtsModelError::BadInput("title contains slurs".to_string()));
}
title = Some(payload.to_string());
}
if let Some(markdown) = request.description_markdown.as_deref() {
if contains_slurs(markdown) {
return Err(EditTtsModelError::BadInput("description contains slurs".to_string()));
}
let markdown = markdown.trim().to_string();
let html = markdown_to_html(&markdown);
description_markdown = Some(markdown);
description_html = Some(html);
}
let ip_address = get_request_ip(&http_request);
let query_result = if editor_is_original_user {
// We need to store the IP address details.
sqlx::query!(
r#"
UPDATE tts_models
SET
title = ?,
description_markdown = ?,
description_rendered_html = ?,
creator_ip_address_last_update = ?,
version = version + 1
WHERE tts_models.token = ?
LIMIT 1
"#,
&title,
&description_markdown,
&description_html,
&ip_address,
&model_record.model_token,
)
.execute(&server_state.mysql_pool)
.await
} else {
// We need to store the moderator details.
sqlx::query!(
r#"
UPDATE tts_models
SET
title = ?,
description_markdown = ?,
description_rendered_html = ?,
maybe_mod_user_token = ?,
version = version + 1
WHERE tts_models.token = ?
LIMIT 1
"#,
&title,
&description_markdown,
&description_html,
&user_session.user_token,
&model_record.model_token,
)
.execute(&server_state.mysql_pool)
.await
};
match query_result {
Ok(_) => {},
Err(err) => {
warn!("Update W2L model edit DB error: {:?}", err);
return Err(EditTtsModelError::ServerError);
}
};
Ok(simple_json_success())
}
+1
View File
@@ -1,3 +1,4 @@
pub mod edit_tts_model;
pub mod enqueue_infer_tts;
pub mod enqueue_upload_tts_model;
pub mod get_tts_inference_job_status;
+7 -1
View File
@@ -71,6 +71,7 @@ use crate::http_server::endpoints::users::edit_profile::edit_profile_handler;
use crate::http_server::endpoints::w2l::set_w2l_template_mod_approval::set_w2l_template_mod_approval_handler;
use crate::http_server::endpoints::w2l::delete_w2l_template::delete_w2l_template_handler;
use crate::http_server::endpoints::w2l::edit_w2l_template::edit_w2l_template_handler;
use crate::http_server::endpoints::tts::edit_tts_model::edit_tts_model_handler;
// TODO TODO TODO TODO
// TODO TODO TODO TODO
@@ -293,6 +294,11 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()>
.route(web::get().to(get_tts_model_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(
web::resource("/model/{model_token}/edit")
.route(web::post().to(edit_tts_model_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(
web::resource("/result/{token}")
.route(web::get().to(get_tts_inference_result_handler))
@@ -333,7 +339,7 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()>
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(
web::resource("/template/{template_token}/edit")
web::resource("/template/{template_token}/edit")
.route(web::post().to(edit_w2l_template_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)