mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-07 18:42:10 +00:00
add endpoint for editing tts models (untested)
This commit is contained in:
1 parent
ec1e280eda
commit
3065f20f9f
5 files changed
+414
-1
No files matched your search
@@ -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;
|
||||
@@ -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,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
@@ -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()))
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user