mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-10 12:01:36 +00:00
Merge pull request #27 from storytold/bt-trending-voices
Bt trending voices
This commit is contained in:
29 files changed
+1049
-21
No files matched your search
Generated
+27
@@ -640,6 +640,33 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "analytics-job"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap 2.34.0",
|
||||
"config",
|
||||
"container_common",
|
||||
"database_queries",
|
||||
"dotenv",
|
||||
"easyenv",
|
||||
"enums",
|
||||
"errors",
|
||||
"hostname",
|
||||
"jobs_common",
|
||||
"log",
|
||||
"rand 0.8.4",
|
||||
"reusable_types",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tokens",
|
||||
"tokio 1.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
|
||||
@@ -39,6 +39,7 @@ members = [
|
||||
"crates/lib/user_input_common",
|
||||
|
||||
# Services: API servers, jobs, workers, etc.
|
||||
"crates/service/job/analytics_job",
|
||||
"crates/service/job/download_job",
|
||||
"crates/service/job/inference_job",
|
||||
"crates/service/job/tts_download_job",
|
||||
|
||||
@@ -78,6 +78,12 @@ RUN SQLX_OFFLINE=true \
|
||||
--release \
|
||||
--bin storyteller-web
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin analytics-job \
|
||||
|
||||
RUN SQLX_OFFLINE=true \
|
||||
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
@@ -149,6 +155,7 @@ RUN echo -n ${GIT_SHA} > GIT_SHA
|
||||
|
||||
# Copy all the binaries.
|
||||
COPY --from=builder /tmp/target/release/storyteller-web /
|
||||
COPY --from=builder /tmp/target/release/analytics-job /
|
||||
COPY --from=builder /tmp/target/release/download-job /
|
||||
COPY --from=builder /tmp/target/release/inference-job /
|
||||
COPY --from=builder /tmp/target/release/tts-download-job /
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE trending_model_analytics;
|
||||
@@ -0,0 +1,40 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Periodically capture analytics on trending models
|
||||
CREATE TABLE trending_model_analytics (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- The type of model. TTS, VC, etc.
|
||||
model_type VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The primary key of the TTS, VC, or other model in question.
|
||||
model_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The name of the window, eg. "last_24_hours", "last_5_hours", etc.
|
||||
window_name VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The measured numerical value of the statistic, eg "500 uses" = 500.
|
||||
numeric_value INT(10) NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== VECTOR CLOCK ==========
|
||||
|
||||
-- Incremented with every update.
|
||||
version INT NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (model_token, model_type, window_name),
|
||||
KEY index_model_type_model_token (model_type, model_token),
|
||||
KEY index_model_type (model_type),
|
||||
KEY index_model_token (model_token),
|
||||
KEY index_numeric_value (numeric_value)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
+12
-17
@@ -7,8 +7,9 @@
|
||||
--
|
||||
select
|
||||
m.token,
|
||||
m.title,
|
||||
m.text_pipeline_type,
|
||||
m.ietf_language_tag,
|
||||
m.title,
|
||||
u.username,
|
||||
r.use_count,
|
||||
m.user_deleted_at,
|
||||
@@ -37,25 +38,18 @@ select m.token, m.title, r.use_count from (
|
||||
join tts_models as m
|
||||
on m.token = r.model_token;
|
||||
|
||||
--
|
||||
-- Top 100 models by use count, last 30 days
|
||||
--
|
||||
select m.token, m.title, m.ietf_language_tag, r.use_count from (
|
||||
select model_token, count(*) as use_count
|
||||
from tts_results
|
||||
where created_at > ( CURDATE() - INTERVAL 30 DAY )
|
||||
group by model_token
|
||||
order by use_count desc
|
||||
limit 500
|
||||
) as r
|
||||
join tts_models as m
|
||||
on m.token = r.model_token;
|
||||
|
||||
--
|
||||
-- Top 500 models by use count, last 5 days
|
||||
-- Limited due to tmux scrollback.
|
||||
--
|
||||
select m.token, m.title, m.ietf_language_tag, u.username, r.use_count from (
|
||||
select
|
||||
m.token,
|
||||
m.text_pipeline_type,
|
||||
m.ietf_language_tag,
|
||||
m.title,
|
||||
u.username,
|
||||
r.use_count
|
||||
from (
|
||||
select model_token, count(*) as use_count
|
||||
from tts_results
|
||||
where created_at > ( CURDATE() - INTERVAL 5 DAY )
|
||||
@@ -74,8 +68,9 @@ order by r.use_count desc
|
||||
--
|
||||
select
|
||||
m.token,
|
||||
m.title,
|
||||
m.text_pipeline_type,
|
||||
m.ietf_language_tag,
|
||||
m.title,
|
||||
u.username,
|
||||
r.use_count,
|
||||
m.user_deleted_at,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod media_uploads;
|
||||
pub mod model_categories;
|
||||
pub mod trending_model_analytics;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod model_type;
|
||||
pub mod window_name;
|
||||
@@ -0,0 +1,63 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(test)]
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
|
||||
/// Used in the `trending_model_analytics` table in a `VARCHAR` field.
|
||||
///
|
||||
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ModelType {
|
||||
/// TTS models
|
||||
Tts,
|
||||
|
||||
// NB: It's assumed we'll use this same system to track
|
||||
// trending VC models too, so the next type would be "VC".
|
||||
}
|
||||
|
||||
// TODO(bt, 2023-01-17): This desperately needs MySQL integration tests!
|
||||
impl_enum_display_and_debug_using_to_str!(ModelType);
|
||||
impl_enum_sqlx_coders!(ModelType);
|
||||
|
||||
/// NB: Legacy API for older code.
|
||||
impl ModelType {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Tts => "tts",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"tts" => Ok(Self::Tts),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::by_table::trending_model_analytics::model_type::ModelType;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(ModelType::Tts, "tts");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_str() {
|
||||
assert_eq!(ModelType::Tts.to_str(), "tts");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
assert_eq!(ModelType::from_str("tts").unwrap(), ModelType::Tts);
|
||||
assert!(ModelType::from_str("foo").is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(test)]
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
|
||||
/// Used in the `window_name` table in a `VARCHAR` field.
|
||||
///
|
||||
/// Contrary to most of this crate and unlike most "enum"-types
|
||||
/// that are inflexible, new window names can be added/removed
|
||||
/// without breaking too much.
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)]
|
||||
pub enum WindowName {
|
||||
/// Analytics over the last three hours
|
||||
#[serde(rename = "last_3_hours")]
|
||||
Last3Hours,
|
||||
|
||||
/// Analytics over the last three hours
|
||||
#[serde(rename = "last_3_days")]
|
||||
Last3Days,
|
||||
|
||||
/// Analytics over all historical records
|
||||
#[serde(rename = "all_time")]
|
||||
AllTime,
|
||||
}
|
||||
|
||||
// TODO(bt, 2023-01-17): This desperately needs MySQL integration tests!
|
||||
impl_enum_display_and_debug_using_to_str!(WindowName);
|
||||
impl_enum_sqlx_coders!(WindowName);
|
||||
|
||||
/// NB: Legacy API for older code.
|
||||
impl WindowName {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Last3Hours => "last_3_hours",
|
||||
Self::Last3Days => "last_3_days",
|
||||
Self::AllTime => "all_time",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"last_3_hours" => Ok(Self::Last3Hours),
|
||||
"last_3_days" => Ok(Self::Last3Days),
|
||||
"all_time" => Ok(Self::AllTime),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::by_table::trending_model_analytics::window_name::WindowName;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
mod serde {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(WindowName::Last3Hours, "last_3_hours");
|
||||
assert_serialization(WindowName::Last3Days, "last_3_days");
|
||||
assert_serialization(WindowName::AllTime, "all_time");
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_methods {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_to_str() {
|
||||
assert_eq!(WindowName::Last3Hours.to_str(), "last_3_hours");
|
||||
assert_eq!(WindowName::Last3Days.to_str(), "last_3_days");
|
||||
assert_eq!(WindowName::AllTime.to_str(), "all_time");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
assert_eq!(WindowName::from_str("last_3_hours").unwrap(), WindowName::Last3Hours);
|
||||
assert_eq!(WindowName::from_str("last_3_days").unwrap(), WindowName::Last3Days);
|
||||
assert_eq!(WindowName::from_str("all_time").unwrap(), WindowName::AllTime);
|
||||
assert!(WindowName::from_str("foo").is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ pub mod ip_bans;
|
||||
pub mod media_uploads;
|
||||
pub mod model_categories;
|
||||
pub mod stats;
|
||||
pub mod trending_model_analytics;
|
||||
pub mod tts;
|
||||
pub mod twitch;
|
||||
pub mod users;
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
use sqlx::{MySql, MySqlPool};
|
||||
use sqlx::pool::PoolConnection;
|
||||
use enums::by_table::trending_model_analytics::model_type::ModelType;
|
||||
use enums::by_table::trending_model_analytics::window_name::WindowName;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
pub struct TrendingModels {
|
||||
pub models: Vec<TrendingModel>,
|
||||
}
|
||||
|
||||
pub struct TrendingModel {
|
||||
pub model_token: TtsModelToken,
|
||||
|
||||
// NB: "en", "es", etc. not "en-US", "es-419", etc.
|
||||
pub ietf_primary_language_subtag: String,
|
||||
|
||||
// Time window
|
||||
pub window_name: WindowName,
|
||||
|
||||
// Count of uses within the window.
|
||||
pub numeric_value: u64,
|
||||
}
|
||||
|
||||
// TODO(bt,2023-01-17): This query is massive, so is not type checked :(
|
||||
|
||||
pub async fn list_trending_tts_models_with_pool(
|
||||
mysql_pool: &MySqlPool,
|
||||
) -> AnyhowResult<TrendingModels> {
|
||||
let mut mysql_connection = mysql_pool.acquire().await?;
|
||||
list_trending_tts_models(&mut mysql_connection).await
|
||||
}
|
||||
|
||||
pub async fn list_trending_tts_models(
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> AnyhowResult<TrendingModels> {
|
||||
let query_parts = vec![
|
||||
// English
|
||||
make_subquery(ModelType::Tts, WindowName::AllTime, "en"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Hours, "en"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Days, "en"),
|
||||
|
||||
// Spanish
|
||||
make_subquery(ModelType::Tts, WindowName::AllTime, "es"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Hours, "es"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Days, "es"),
|
||||
|
||||
// Italian
|
||||
make_subquery(ModelType::Tts, WindowName::AllTime, "it"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Hours, "it"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Days, "it"),
|
||||
|
||||
// French
|
||||
make_subquery(ModelType::Tts, WindowName::AllTime, "fr"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Hours, "fr"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Days, "fr"),
|
||||
|
||||
// German
|
||||
make_subquery(ModelType::Tts, WindowName::AllTime, "de"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Hours, "de"),
|
||||
make_subquery(ModelType::Tts, WindowName::Last3Days, "de"),
|
||||
];
|
||||
|
||||
let query = query_parts.join(" UNION ");
|
||||
let mut query = sqlx::query_as::<_, RawTrendingModel>(&query);
|
||||
|
||||
let results = query.fetch_all(mysql_connection).await?;
|
||||
|
||||
let results = results.into_iter()
|
||||
.map(|model| TrendingModel {
|
||||
model_token: TtsModelToken::new(model.model_token),
|
||||
ietf_primary_language_subtag: model.ietf_primary_language_subtag,
|
||||
window_name: WindowName::from_str(&model.window_name).unwrap_or(WindowName::AllTime), // TODO: Fail properly
|
||||
numeric_value: model.numeric_value as u64,
|
||||
})
|
||||
.collect::<Vec<TrendingModel>>();
|
||||
|
||||
Ok(TrendingModels {
|
||||
models: results,
|
||||
})
|
||||
}
|
||||
|
||||
// Build queries meant to be UNION-d together.
|
||||
pub fn make_subquery(model_type: ModelType, window_name: WindowName, ietf_primary_language_subtag: &str) -> String {
|
||||
format!(r#"
|
||||
(
|
||||
SELECT
|
||||
m.token as model_token,
|
||||
m.ietf_primary_language_subtag,
|
||||
t.window_name,
|
||||
t.numeric_value
|
||||
|
||||
FROM trending_model_analytics AS t
|
||||
JOIN tts_models AS m
|
||||
ON m.token = t.model_token
|
||||
|
||||
WHERE
|
||||
t.model_type = "{}"
|
||||
AND t.window_name = "{}"
|
||||
AND m.ietf_primary_language_subtag = "{}"
|
||||
|
||||
ORDER BY numeric_value DESC
|
||||
LIMIT 10
|
||||
)
|
||||
"#,
|
||||
model_type,
|
||||
window_name,
|
||||
ietf_primary_language_subtag)
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct RawTrendingModel {
|
||||
pub model_token: String,
|
||||
pub ietf_primary_language_subtag: String,
|
||||
pub window_name: String,
|
||||
pub numeric_value: i64,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod list_trending_tts_models;
|
||||
pub mod upsert_trending_model_analytics;
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
use container_common::anyhow_result::AnyhowResult;
|
||||
use enums::by_table::trending_model_analytics::model_type::ModelType;
|
||||
use enums::by_table::trending_model_analytics::window_name::WindowName;
|
||||
use sqlx::MySql;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
// NB: Only TTS for now. New enum variants for other token types.
|
||||
pub enum ModelToken<'a> {
|
||||
Tts(&'a TtsModelToken),
|
||||
}
|
||||
|
||||
pub struct Args<'a> {
|
||||
pub model_token: ModelToken<'a>,
|
||||
pub window_name: WindowName,
|
||||
pub numeric_value: u64,
|
||||
pub mysql_connection: &'a mut PoolConnection<MySql>,
|
||||
}
|
||||
|
||||
pub async fn upsert_trending_model_analytics(args: Args<'_>) -> AnyhowResult<()> {
|
||||
|
||||
let (model_type, model_token) = match args.model_token {
|
||||
ModelToken::Tts(ref token) => (ModelType::Tts, token.as_str()),
|
||||
};
|
||||
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO trending_model_analytics
|
||||
SET
|
||||
model_type = ?,
|
||||
model_token = ?,
|
||||
window_name = ?,
|
||||
numeric_value = ?,
|
||||
version = 1
|
||||
|
||||
ON DUPLICATE KEY UPDATE
|
||||
numeric_value = ?,
|
||||
version = version + 1
|
||||
"#,
|
||||
// Insert
|
||||
model_type,
|
||||
model_token,
|
||||
args.window_name,
|
||||
args.numeric_value,
|
||||
args.numeric_value,
|
||||
);
|
||||
|
||||
let _r = query.execute(args.mysql_connection).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//! NB: This query is potentially long-lived and not suitable for low-latency HTTP endpoints.
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use sqlx::MySql;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
pub struct TtsModelUseCountInfo {
|
||||
pub use_count: u64,
|
||||
}
|
||||
|
||||
pub async fn count_tts_model_uses(
|
||||
tts_model_token: &TtsModelToken,
|
||||
last_n_minutes: u64,
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> AnyhowResult<TtsModelUseCountInfo> {
|
||||
|
||||
info!("Querying for TTS model {} use count ({} minutes)", tts_model_token, last_n_minutes);
|
||||
|
||||
let use_count = sqlx::query_as!(
|
||||
RawTtsModelUseCountInfo,
|
||||
r#"
|
||||
SELECT
|
||||
count(*) AS use_count
|
||||
FROM
|
||||
tts_results
|
||||
WHERE
|
||||
created_at > ( CURDATE() - INTERVAL ? MINUTE )
|
||||
AND model_token = ?
|
||||
"#,
|
||||
last_n_minutes,
|
||||
tts_model_token
|
||||
)
|
||||
.fetch_one(mysql_connection)
|
||||
.await?;
|
||||
|
||||
Ok(TtsModelUseCountInfo {
|
||||
use_count: use_count.use_count as u64,
|
||||
})
|
||||
}
|
||||
|
||||
struct RawTtsModelUseCountInfo {
|
||||
pub use_count: i64,
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//! NB: This query is potentially long-lived and not suitable for low-latency HTTP endpoints.
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use sqlx::MySql;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
pub struct TtsModelTotalUseCountInfo {
|
||||
pub total_use_count: u64,
|
||||
}
|
||||
|
||||
pub async fn count_tts_model_uses_total(
|
||||
tts_model_token: &TtsModelToken,
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> AnyhowResult<TtsModelTotalUseCountInfo> {
|
||||
|
||||
info!("Querying for TTS model {} total use count", tts_model_token);
|
||||
|
||||
let total_use_count = sqlx::query_as!(
|
||||
RawTtsModelTotalUseCountInfo,
|
||||
r#"
|
||||
SELECT
|
||||
count(*) AS total_use_count
|
||||
FROM
|
||||
tts_results
|
||||
WHERE
|
||||
model_token = ?
|
||||
"#,
|
||||
tts_model_token
|
||||
)
|
||||
.fetch_one(mysql_connection)
|
||||
.await?;
|
||||
|
||||
Ok(TtsModelTotalUseCountInfo {
|
||||
total_use_count: total_use_count.total_use_count as u64,
|
||||
})
|
||||
}
|
||||
|
||||
struct RawTtsModelTotalUseCountInfo {
|
||||
pub total_use_count: i64,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use log::info;
|
||||
use errors::AnyhowResult;
|
||||
use sqlx::MySql;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
/// List of all TTS model tokens.
|
||||
/// This is typically used to run jobs that calculate analytics on models.
|
||||
/// Currently does not need to be batched, but future scale may dictate batching.
|
||||
#[derive(Clone)]
|
||||
pub struct TtsModelTokens {
|
||||
pub tokens: Vec<TtsModelTokenInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TtsModelTokenInfo {
|
||||
pub token: TtsModelToken,
|
||||
//pub ietf_language_tag: String,
|
||||
//pub ietf_primary_language_subtag: String,
|
||||
//pub creator_set_visibility: Visibility,
|
||||
//pub maybe_user_deleted_at: Option<DateTime<Utc>>,
|
||||
//pub maybe_mod_deleted_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
pub async fn list_all_tts_model_tokens(mysql_connection: &mut PoolConnection<MySql>) -> AnyhowResult<TtsModelTokens> {
|
||||
info!("Querying for all TTS model tokens");
|
||||
|
||||
let tokens = sqlx::query_as!(
|
||||
RawTtsModelTokenInfo,
|
||||
r#"
|
||||
SELECT
|
||||
token as `token: tokens::tokens::tts_models::TtsModelToken`
|
||||
FROM tts_models
|
||||
"#)
|
||||
.fetch_all(mysql_connection)
|
||||
.await?;
|
||||
|
||||
let tokens = tokens.into_iter()
|
||||
.map(|token| TtsModelTokenInfo {
|
||||
token: token.token,
|
||||
})
|
||||
.collect::<Vec<TtsModelTokenInfo>>();
|
||||
|
||||
Ok(TtsModelTokens {
|
||||
tokens,
|
||||
})
|
||||
}
|
||||
|
||||
struct RawTtsModelTokenInfo {
|
||||
pub token: TtsModelToken,
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
pub mod count_tts_model_uses;
|
||||
pub mod count_tts_model_uses_total;
|
||||
pub mod delete_tts_model_various_scopes;
|
||||
pub mod edit_tts_model_details;
|
||||
pub mod edit_tts_model_moderator_details;
|
||||
pub mod get_tts_model;
|
||||
pub mod get_tts_model_for_inference;
|
||||
pub mod insert_tts_model_from_download_job;
|
||||
pub mod list_tts_models;
|
||||
pub mod list_all_tts_model_tokens;
|
||||
pub mod list_tts_models;
|
||||
@@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "analytics-job"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
"Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>",
|
||||
]
|
||||
|
||||
[[bin]]
|
||||
name = "analytics-job"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Internal
|
||||
config = { path = "../../../lib/config" }
|
||||
container_common = { path = "../../../lib/container_common" }
|
||||
database_queries = { path = "../../../lib/database_queries" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
jobs_common = { path = "../../../lib/jobs_common" }
|
||||
reusable_types = { path = "../../../_types/reusable_types" }
|
||||
tokens = { path = "../../../_types/tokens" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.40"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
|
||||
dotenv = "0.15.0"
|
||||
easyenv = "0.0.6"
|
||||
hostname = "0.3.1"
|
||||
log = "0.4.14"
|
||||
rand = "0.8.3"
|
||||
|
||||
# MySql Database
|
||||
# "offline" supports typesafe builds without a live database
|
||||
# "rustls" doesn't appear to work with IP addresses, per:
|
||||
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
|
||||
sqlx = { version = "0.6.2", features = [ "mysql", "runtime-actix-native-tls", "offline", "chrono" ] }
|
||||
|
||||
# Runtime
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
tokio = { version = "1.6.0", features = ["macros"] }
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
serde_derive = "1.0.125"
|
||||
serde_json = "1.0.64"
|
||||
@@ -0,0 +1,22 @@
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
pub struct JobState {
|
||||
pub mysql_pool: MySqlPool,
|
||||
|
||||
pub sleep_config: SleepConfigs,
|
||||
}
|
||||
|
||||
/// Use sleep to not overload the database.
|
||||
pub struct SleepConfigs {
|
||||
// How long to wait between individual "jobs".
|
||||
pub between_job_wait_millis: u64,
|
||||
|
||||
// How long to wait between individual job batches.
|
||||
pub between_job_batch_wait_millis: u64,
|
||||
|
||||
// How long to wait between individual queries.
|
||||
pub between_query_wait_millis: u64,
|
||||
|
||||
// How long to wait between errors.
|
||||
pub between_error_wait_millis: u64,
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use crate::job_state::JobState;
|
||||
use database_queries::queries::trending_model_analytics::upsert_trending_model_analytics::{Args, ModelToken, upsert_trending_model_analytics};
|
||||
use database_queries::queries::tts::tts_models::count_tts_model_uses::count_tts_model_uses;
|
||||
use database_queries::queries::tts::tts_models::count_tts_model_uses_total::count_tts_model_uses_total;
|
||||
use database_queries::queries::tts::tts_models::list_all_tts_model_tokens::{list_all_tts_model_tokens, TtsModelTokens};
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use sqlx::MySql;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use std::time::Duration;
|
||||
use enums::by_table::trending_model_analytics::window_name::WindowName;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
// TODO(bt, 2023-01-16): Make a job trait and make this an instance.
|
||||
// Jobs can store state, batch progress, and not starve one another.
|
||||
// It'll be easier to interleave jobs.
|
||||
|
||||
pub async fn calculate_model_analytics(job_state: &JobState) -> AnyhowResult<()> {
|
||||
// TODO(bt, 2023-01-16): It's conceivable the pool connection dies mid-workload and we starve
|
||||
// our tokens. An ideal fix would be a self-healing pool connection, but for now we'll use
|
||||
// ugly hacks.
|
||||
let mut mysql_connection = job_state.mysql_pool.acquire().await?;
|
||||
|
||||
let tokens = query_all_model_tokens(&mut mysql_connection).await?;
|
||||
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
|
||||
for token in tokens.tokens {
|
||||
info!("Running analytics for TTS model {}", token.token);
|
||||
|
||||
let _r = query_single_model_statistics(
|
||||
job_state,
|
||||
&mut mysql_connection,
|
||||
&token.token).await;
|
||||
|
||||
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_job_wait_millis));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn query_all_model_tokens(
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> AnyhowResult<TtsModelTokens> {
|
||||
let mut tokens = list_all_tts_model_tokens(mysql_connection).await?;
|
||||
|
||||
// NB: This is just a hack to not starve the work queue. We should replace this with a
|
||||
// self-healing connection and batch cursors.
|
||||
let mut rng = thread_rng();
|
||||
tokens.tokens.shuffle(&mut rng);
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
async fn query_single_model_statistics(
|
||||
job_state: &JobState,
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
model_token: &TtsModelToken,
|
||||
) -> AnyhowResult<()> {
|
||||
{
|
||||
let three_hours_in_minutes = 60 * 3;
|
||||
|
||||
let result = count_tts_model_uses(
|
||||
model_token,
|
||||
three_hours_in_minutes,
|
||||
mysql_connection).await?;
|
||||
|
||||
info!("TTS model {} uses {} times (three hours)", model_token, result.use_count);
|
||||
|
||||
let _r = upsert_trending_model_analytics(Args {
|
||||
model_token: ModelToken::Tts(model_token),
|
||||
window_name: WindowName::Last3Hours,
|
||||
numeric_value: result.use_count,
|
||||
mysql_connection,
|
||||
}).await?;
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_query_wait_millis));
|
||||
|
||||
{
|
||||
let three_days_in_minutes = 60 * 24 * 3;
|
||||
|
||||
let result = count_tts_model_uses(
|
||||
model_token,
|
||||
three_days_in_minutes,
|
||||
mysql_connection).await?;
|
||||
|
||||
info!("TTS model {} uses {} times (three days)", model_token, result.use_count);
|
||||
|
||||
let _r = upsert_trending_model_analytics(Args {
|
||||
model_token: ModelToken::Tts(model_token),
|
||||
window_name: WindowName::Last3Days,
|
||||
numeric_value: result.use_count,
|
||||
mysql_connection,
|
||||
}).await?;
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_query_wait_millis));
|
||||
|
||||
{
|
||||
let result = count_tts_model_uses_total(
|
||||
model_token,
|
||||
mysql_connection).await?;
|
||||
|
||||
info!("TTS model {} uses {} times (total)", model_token, result.total_use_count);
|
||||
|
||||
let _r = upsert_trending_model_analytics(Args {
|
||||
model_token: ModelToken::Tts(model_token),
|
||||
window_name: WindowName::AllTime,
|
||||
numeric_value: result.total_use_count,
|
||||
mysql_connection,
|
||||
}).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod calculate_model_analytics;
|
||||
@@ -0,0 +1,71 @@
|
||||
// Never allow these
|
||||
#![forbid(private_in_public)]
|
||||
#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things
|
||||
|
||||
// Okay to toggle
|
||||
#![forbid(unreachable_patterns)]
|
||||
#![forbid(unused_imports)]
|
||||
#![forbid(unused_mut)]
|
||||
#![forbid(unused_variables)]
|
||||
|
||||
// Always allow
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
// Strict
|
||||
//#![forbid(warnings)]
|
||||
|
||||
pub mod job_state;
|
||||
pub mod jobs;
|
||||
pub mod main_loop;
|
||||
|
||||
use config::shared_constants::DEFAULT_MYSQL_CONNECTION_STRING;
|
||||
use config::shared_constants::DEFAULT_RUST_LOG;
|
||||
use crate::job_state::{JobState, SleepConfigs};
|
||||
use crate::main_loop::main_loop;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> AnyhowResult<()> {
|
||||
easyenv::init_all_with_default_logging(Some(DEFAULT_RUST_LOG));
|
||||
|
||||
let _ = dotenv::from_filename(".env-analytics-job").ok(); // NB: Specific to `analytics-job` app.
|
||||
let _ = dotenv::from_filename(".env-secrets").ok(); // NB: Secrets not to live in source control.
|
||||
|
||||
info!("Obtaining hostname...");
|
||||
|
||||
let server_hostname = hostname::get()
|
||||
.ok()
|
||||
.and_then(|h| h.into_string().ok())
|
||||
.unwrap_or("analytics-job".to_string());
|
||||
|
||||
info!("Hostname: {}", &server_hostname);
|
||||
|
||||
let db_connection_string =
|
||||
easyenv::get_env_string_or_default(
|
||||
"MYSQL_URL",
|
||||
DEFAULT_MYSQL_CONNECTION_STRING);
|
||||
|
||||
info!("Connecting to database...");
|
||||
|
||||
let mysql_pool = MySqlPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&db_connection_string)
|
||||
.await?;
|
||||
|
||||
let job_state = JobState {
|
||||
mysql_pool,
|
||||
sleep_config: SleepConfigs {
|
||||
between_job_wait_millis: easyenv::get_env_num("BETWEEN_JOB_WAIT_MILLIS", 100)?,
|
||||
between_job_batch_wait_millis: easyenv::get_env_num("BETWEEN_JOB_BATCH_WAIT_MILLIS", 5000)?,
|
||||
between_query_wait_millis: easyenv::get_env_num("BETWEEN_QUERY_WAIT_MILLIS", 100)?,
|
||||
between_error_wait_millis: easyenv::get_env_num("BETWEEN_ERROR_WAIT_MILLIS", 10_000)?,
|
||||
},
|
||||
};
|
||||
|
||||
let _r = main_loop(job_state).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use crate::job_state::JobState;
|
||||
use crate::jobs::calculate_model_analytics::calculate_model_analytics;
|
||||
use errors::AnyhowResult;
|
||||
use std::time::Duration;
|
||||
use log::{error, info};
|
||||
|
||||
pub async fn main_loop(job_state: JobState) -> AnyhowResult<()> {
|
||||
loop {
|
||||
info!("Begin analytics job batch.");
|
||||
|
||||
match calculate_model_analytics(&job_state).await {
|
||||
Ok(_) => {
|
||||
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_job_batch_wait_millis));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error: {:?}", e);
|
||||
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_error_wait_millis));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-3
@@ -26,9 +26,10 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use chrono::{DateTime, Utc};
|
||||
use database_queries::queries::trending_model_analytics::list_trending_tts_models::{list_trending_tts_models, TrendingModels};
|
||||
use tokens::tokens::model_categories::ModelCategoryToken;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
use crate::model::categories::synthetic_category_list::SYNTHETIC_CATEGORY_LATEST_TTS_MODELS;
|
||||
use crate::model::categories::synthetic_category_list::{SYNTHETIC_CATEGORY_LATEST_TTS_MODELS, SYNTHETIC_CATEGORY_TRENDING_TTS_MODELS};
|
||||
|
||||
// =============== Success Response ===============
|
||||
|
||||
@@ -177,7 +178,12 @@ async fn query_and_construct_payload(
|
||||
category_cache: &SingleItemTtlCache<CategoryList>,
|
||||
mysql_pool: &MySqlPool
|
||||
) -> Result<ModelTokensByCategoryToken, ListFullyComputedAssignedTtsCategoriesError> {
|
||||
let (categories, models, model_category_map) = {
|
||||
let (
|
||||
categories,
|
||||
models,
|
||||
model_category_map,
|
||||
trending_models
|
||||
) = {
|
||||
let mut mysql_connection = mysql_pool.acquire()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -206,12 +212,20 @@ async fn query_and_construct_payload(
|
||||
ListFullyComputedAssignedTtsCategoriesError::ServerError
|
||||
})?;
|
||||
|
||||
(categories, models, model_category_map)
|
||||
let trending_models = list_trending_tts_models(&mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Error querying trending TTS models: {:?}", e);
|
||||
ListFullyComputedAssignedTtsCategoriesError::ServerError
|
||||
})?;
|
||||
|
||||
(categories, models, model_category_map, trending_models)
|
||||
};
|
||||
|
||||
let mut recursive_category_to_model_map = recursive_category_to_model_map(&model_category_map, &categories);
|
||||
|
||||
add_recent_models(&mut recursive_category_to_model_map, models);
|
||||
add_trending_models(&mut recursive_category_to_model_map, trending_models);
|
||||
|
||||
Ok(ModelTokensByCategoryToken {
|
||||
recursive: recursive_category_to_model_map,
|
||||
@@ -435,6 +449,19 @@ fn add_recent_models(recursive_category_to_model_map: &mut CategoryTokenToModelT
|
||||
recursive_category_to_model_map.insert(synthetic_token, model_tokens);
|
||||
}
|
||||
|
||||
fn add_trending_models(recursive_category_to_model_map: &mut CategoryTokenToModelTokensMap, trending_models: TrendingModels) {
|
||||
// TODO: This is a grossly bad guess.
|
||||
let model_tokens = trending_models.models.iter()
|
||||
.take(30)
|
||||
.map(|trending_model| {
|
||||
trending_model.model_token.clone()
|
||||
})
|
||||
.collect::<BTreeSet<TtsModelToken>>();
|
||||
|
||||
let synthetic_token = ModelCategoryToken::new_from_str(SYNTHETIC_CATEGORY_TRENDING_TTS_MODELS.category_token);
|
||||
recursive_category_to_model_map.insert(synthetic_token, model_tokens);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// TODO: This really needs tests!
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod misc;
|
||||
pub mod moderation;
|
||||
pub mod service;
|
||||
pub mod stubs;
|
||||
pub mod trending;
|
||||
pub mod tts;
|
||||
pub mod twitch;
|
||||
pub mod vocoders;
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
// NB: Incrementally getting rid of build warnings...
|
||||
//#![forbid(unused_imports)]
|
||||
//#![forbid(unused_mut)]
|
||||
//#![forbid(unused_variables)]
|
||||
|
||||
//! This endpoint recursively calculates (and caches) the list of every category a TTS model
|
||||
//! belongs to. This saves an enormous amount of clientside CPU compute.
|
||||
//!
|
||||
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpResponse, HttpRequest};
|
||||
use chrono::{DateTime, Utc};
|
||||
use container_common::anyhow_result::AnyhowResult;
|
||||
use crate::model::cached_queries::list_cached_tts_categories_for_public_dropdown::list_cached_tts_categories_for_public_dropdown;
|
||||
use crate::model::categories::synthetic_category_list::SYNTHETIC_CATEGORY_LATEST_TTS_MODELS;
|
||||
use crate::server_state::ServerState;
|
||||
use database_queries::queries::model_categories::list_categories_query_builder::CategoryList;
|
||||
use database_queries::queries::trending_model_analytics::list_trending_tts_models::{list_trending_tts_models, list_trending_tts_models_with_pool};
|
||||
use database_queries::queries::tts::tts_category_assignments::fetch_and_build_tts_model_category_map::fetch_and_build_tts_model_category_map_with_connection;
|
||||
use database_queries::queries::tts::tts_models::list_tts_models::list_tts_models_with_connection;
|
||||
use enums::by_table::trending_model_analytics::window_name::WindowName;
|
||||
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
|
||||
use log::{info, error};
|
||||
use memory_caching::single_item_ttl_cache::SingleItemTtlCache;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{MySql, MySqlPool};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use tokens::tokens::model_categories::ModelCategoryToken;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
// TODO TODO TODO: This endpoint is not done!
|
||||
// TODO TODO TODO: This endpoint is not done!
|
||||
// TODO TODO TODO: This endpoint is not done!
|
||||
// TODO TODO TODO: This endpoint is not done!
|
||||
// TODO TODO TODO: This endpoint is not done!
|
||||
// TODO TODO TODO: This endpoint is not done!
|
||||
|
||||
// =============== Success Response ===============
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ListTrendingTtsModelsResponse {
|
||||
pub success: bool,
|
||||
|
||||
pub top_trending: WindowTrends,
|
||||
|
||||
pub top_trending_by_language_code: HashMap<String, WindowTrends>,
|
||||
}
|
||||
|
||||
pub type WindowTrends = HashMap<WindowName, Vec<TtsModelToken>>;
|
||||
|
||||
|
||||
// =============== Error Response ===============
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum ListTrendingTtsModelsError {
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for ListTrendingTtsModelsError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
ListTrendingTtsModelsError::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 ListTrendingTtsModelsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Handler ===============
|
||||
|
||||
pub async fn list_trending_tts_models_handler(
|
||||
_http_request: HttpRequest,
|
||||
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, ListTrendingTtsModelsError>
|
||||
{
|
||||
|
||||
// TODO: Cache the outputs!
|
||||
|
||||
// let maybe_category_assignments = server_state.caches.tts_model_category_assignments.copy_without_bump_if_unexpired()
|
||||
// .map_err(|e| {
|
||||
// error!("Error consulting cache: {:?}", e);
|
||||
// ListTrendingTtsModelsError::ServerError
|
||||
// })?;
|
||||
//
|
||||
// let category_assignments = match maybe_category_assignments {
|
||||
// Some(category_assignments) => {
|
||||
// info!("Serving TTS category assignments from cache");
|
||||
// category_assignments
|
||||
// },
|
||||
// None => {
|
||||
// let category_assignments = query_and_construct_payload(
|
||||
// &server_state.caches.database_tts_category_list,
|
||||
// &server_state.mysql_pool)
|
||||
// .await?;
|
||||
//
|
||||
// server_state.caches.tts_model_category_assignments.store_copy(&category_assignments)
|
||||
// .map_err(|e| {
|
||||
// error!("Error storing cache: {:?}", e);
|
||||
// ListTrendingTtsModelsError::ServerError
|
||||
// })?;
|
||||
//
|
||||
// category_assignments
|
||||
// },
|
||||
// };
|
||||
|
||||
let trending_models= list_trending_tts_models_with_pool(&server_state.mysql_pool).await
|
||||
.map_err(|e| {
|
||||
error!("Query error: {:?}", e);
|
||||
ListTrendingTtsModelsError::ServerError
|
||||
})?;
|
||||
|
||||
// TODO: Actually generate the response body sensibly.
|
||||
|
||||
let mut top_trending = HashMap::new();
|
||||
let mut top_trending_by_language_code = HashMap::new();
|
||||
|
||||
for trending_model in trending_models.models.iter() {
|
||||
if !top_trending_by_language_code.contains_key(&trending_model.ietf_primary_language_subtag) {
|
||||
top_trending_by_language_code.insert(trending_model.ietf_primary_language_subtag.clone(), HashMap::new());
|
||||
}
|
||||
|
||||
if let Some(window_map) = top_trending_by_language_code.get_mut(&trending_model.ietf_primary_language_subtag) {
|
||||
if !window_map.contains_key(&trending_model.window_name) {
|
||||
window_map.insert(trending_model.window_name, Vec::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let response = ListTrendingTtsModelsResponse {
|
||||
success: true,
|
||||
top_trending,
|
||||
top_trending_by_language_code,
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|_e| ListTrendingTtsModelsError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod list_trending_tts_models;
|
||||
@@ -52,6 +52,7 @@ use crate::http_server::endpoints::stubs::app_model_downloads::get_app_model_dow
|
||||
use crate::http_server::endpoints::stubs::app_news::get_app_news_handler;
|
||||
use crate::http_server::endpoints::stubs::app_plans::get_app_plans_handler;
|
||||
use crate::http_server::endpoints::stubs::post_app_analytics::post_app_analytics_handler;
|
||||
use crate::http_server::endpoints::trending::list_trending_tts_models::list_trending_tts_models_handler;
|
||||
use crate::http_server::endpoints::tts::delete_tts_model::delete_tts_model_handler;
|
||||
use crate::http_server::endpoints::tts::delete_tts_result::delete_tts_inference_result_handler;
|
||||
use crate::http_server::endpoints::tts::edit_tts_model::edit_tts_model_handler;
|
||||
@@ -128,6 +129,7 @@ pub fn add_routes<T, B> (app: App<T, B>) -> App<T, B>
|
||||
app = add_flag_routes(app); /* /flag */
|
||||
app = add_desktop_app_routes(app); /* /v1/vc/... */
|
||||
app = add_media_upload_routes(app); /* /v1/media_upload/... */
|
||||
app = add_trending_routes(app); /* /v1/trending/... */
|
||||
|
||||
// From components
|
||||
app = add_suggested_api_v1_account_creation_and_session_routes(app); // /create_account, /session, /login, /logout
|
||||
@@ -905,6 +907,28 @@ fn add_media_upload_routes<T, B> (app: App<T, B>) -> App<T, B>
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== TRENDING ROUTES ====================
|
||||
|
||||
fn add_trending_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("/v1/trending")
|
||||
.service(web::resource("/tts_models")
|
||||
.route(web::get().to(list_trending_tts_models_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// ==================== TWITCH ROUTES ====================
|
||||
|
||||
// TODO: Maybe move these into an "oauth-gateway" type http service.
|
||||
|
||||
Reference in new issue
Block a user