Merge pull request #95 from storytold/oct-12-mithoo

super basic favorites impl
This commit is contained in:
Brandon Thomas authored and GitHub committed 2023-11-21 18:03:58 -05:00
commit a1191aa2c3
26 files changed
+1524 -4

No files matched your search

@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE favorites;
@@ -0,0 +1,46 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Favorites that can be attached to just about anything.
-- This is a polymorphic table and can apply to a wide variety of entities.
CREATE TABLE favorites
(
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- User making the favorite
user_token VARCHAR(32) NOT NULL,
-- The type of entity the favorite applies to
entity_type VARCHAR(32) NOT NULL,
-- The token of the entity the favorite applies to
entity_token VARCHAR(32) NOT NULL,
-- ========== 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,
deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY index_entity_type_entity_token (entity_type, entity_token),
KEY index_user_token_entity_token (user_token, entity_type),
KEY index_user_token (user_token),
KEY index_created_at (created_at),
KEY index_user_deleted_at (deleted_at)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_bin;
@@ -0,0 +1,128 @@
use serde::Deserialize;
use serde::Serialize;
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
/// Used in the `favorites` table in a `VARCHAR(32)` field named `entity_type`.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum FavoriteEntityType {
/// User
#[serde(rename = "user")]
User,
/// TTS model (architecture does not matter)
#[serde(rename = "tts_model")]
TtsModel,
/// TTS result (architecture does not matter)
#[serde(rename = "tts_result")]
TtsResult,
/// W2L template
#[serde(rename = "w2l_template")]
W2lTemplate,
/// W2L result
#[serde(rename = "w2l_result")]
W2lResult,
/// MediaFile
#[serde(rename = "media_file")]
MediaFile,
/// VoiceConversionModel
#[serde(rename = "voice_conversion_model")]
VoiceConversionModel,
/// ZsVoice
#[serde(rename = "zs_voice")]
ZsVoice,
}
// TODO(bt, 2023-01-17): This desperately needs MySQL integration tests!
impl_enum_display_and_debug_using_to_str!(FavoriteEntityType);
impl_mysql_enum_coders!(FavoriteEntityType);
/// NB: Legacy API for older code.
impl FavoriteEntityType {
pub fn to_str(&self) -> &'static str {
match self {
Self::User => "user",
Self::TtsModel => "tts_model",
Self::TtsResult => "tts_result",
Self::W2lTemplate => "w2l_template",
Self::W2lResult => "w2l_result",
Self::MediaFile => "media_file",
Self::VoiceConversionModel => "voice_conversion_model",
Self::ZsVoice => "zs_voice",
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"user" => Ok(Self::User),
"tts_model" => Ok(Self::TtsModel),
"tts_result" => Ok(Self::TtsResult),
"w2l_template" => Ok(Self::W2lTemplate),
"w2l_result" => Ok(Self::W2lResult),
"media_file" => Ok(Self::MediaFile),
"voice_conversion_model" => Ok(Self::VoiceConversionModel),
"zs_voice" => Ok(Self::ZsVoice),
_ => Err(format!("invalid value: {:?}", value)),
}
}
}
#[cfg(test)]
mod tests {
use crate::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use crate::test_helpers::assert_serialization;
mod serde {
use super::*;
#[test]
fn test_serialization() {
assert_serialization(FavoriteEntityType::User, "user");
assert_serialization(FavoriteEntityType::TtsModel, "tts_model");
assert_serialization(FavoriteEntityType::TtsResult, "tts_result");
assert_serialization(FavoriteEntityType::W2lTemplate, "w2l_template");
assert_serialization(FavoriteEntityType::W2lResult, "w2l_result");
assert_serialization(FavoriteEntityType::MediaFile, "media_file");
assert_serialization(FavoriteEntityType::VoiceConversionModel, "voice_conversion_model");
assert_serialization(FavoriteEntityType::ZsVoice, "zs_voice");
}
}
mod impl_methods {
use super::*;
#[test]
fn test_to_str() {
assert_eq!(FavoriteEntityType::User.to_str(), "user");
assert_eq!(FavoriteEntityType::TtsModel.to_str(), "tts_model");
assert_eq!(FavoriteEntityType::TtsResult.to_str(), "tts_result");
assert_eq!(FavoriteEntityType::W2lTemplate.to_str(), "w2l_template");
assert_eq!(FavoriteEntityType::W2lResult.to_str(), "w2l_result");
assert_eq!(FavoriteEntityType::MediaFile.to_str(), "media_file");
assert_eq!(FavoriteEntityType::VoiceConversionModel.to_str(), "voice_conversion_model");
assert_eq!(FavoriteEntityType::ZsVoice.to_str(), "zs_voice");
}
#[test]
fn test_from_str() {
assert_eq!(FavoriteEntityType::from_str("user").unwrap(), FavoriteEntityType::User);
assert_eq!(FavoriteEntityType::from_str("tts_model").unwrap(), FavoriteEntityType::TtsModel);
assert_eq!(FavoriteEntityType::from_str("tts_result").unwrap(), FavoriteEntityType::TtsResult);
assert_eq!(FavoriteEntityType::from_str("w2l_template").unwrap(), FavoriteEntityType::W2lTemplate);
assert_eq!(FavoriteEntityType::from_str("w2l_result").unwrap(), FavoriteEntityType::W2lResult);
assert_eq!(FavoriteEntityType::from_str("media_file").unwrap(), FavoriteEntityType::MediaFile);
assert_eq!(FavoriteEntityType::from_str("voice_conversion_model").unwrap(), FavoriteEntityType::VoiceConversionModel);
assert_eq!(FavoriteEntityType::from_str("zs_voice").unwrap(), FavoriteEntityType::ZsVoice);
assert!(FavoriteEntityType::from_str("foo").is_err());
}
}
}
@@ -0,0 +1 @@
pub mod favorite_entity_type;
+1
View File
@@ -19,3 +19,4 @@ pub mod zs_voices;
pub mod tts_render_tasks;
pub mod web_rendition_targets;
pub mod web_scraping_targets;
pub mod favorites;
+2
View File
@@ -27,6 +27,7 @@ pub(crate) enum TokenPrefix {
Comment,
DownloadJob,
EmailSenderJob,
Favorite,
InferenceJob,
MediaFile,
MediaUpload,
@@ -93,6 +94,7 @@ impl PrefixGenerator for TokenPrefix {
Self::DownloadJob => "jdown_", // NB: Previously "JGUP:"
Self::EmailSenderJob => "email_job_",
Self::InferenceJob => "jinf_",
Self::Favorite => "fav_",
Self::MediaFile => "m_",
Self::MediaUpload => "mu_",
Self::ModelWeight => "weight_",
@@ -0,0 +1,14 @@
use std::fmt::Debug;
use serde::Deserialize;
use serde::Serialize;
use crate::prefixes::TokenPrefix;
/// The primary key for Audit Logs.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize)]
#[sqlx(transparent)]
pub struct FavoriteToken(pub String);
impl_string_token!(FavoriteToken);
impl_crockford_generator!(FavoriteToken, 32usize, TokenPrefix::Favorite, CrockfordLower);
+1
View File
@@ -34,3 +34,4 @@ pub mod w2l_templates;
pub mod zs_voice_dataset_samples;
pub mod zs_voice_datasets;
pub mod zs_voices;
pub mod favorites;
@@ -0,0 +1,63 @@
use std::marker::PhantomData;
use anyhow::anyhow;
use sqlx::{Executor, MySql};
use errors::AnyhowResult;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::users::UserToken;
use crate::queries::favorites::favorite_entity_token::FavoriteEntityToken;
pub struct CreateFavoriteArgs<'e, 'c, E>
where E: 'e + Executor<'c, Database = MySql>
{
pub entity_token: &'e FavoriteEntityToken,
pub user_token: &'e UserToken,
pub mysql_executor: E,
// TODO: Not sure if this works to tell the compiler we need the lifetime annotation.
// See: https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-lifetime-parameters
pub phantom: PhantomData<&'c E>,
}
pub async fn create_favorite<'e, 'c : 'e, E>(
args: CreateFavoriteArgs<'e, 'c, E>,
)
-> AnyhowResult<FavoriteToken>
where E: 'e + Executor<'c, Database = MySql>
{
let favorite_token = FavoriteToken::generate();
let (entity_type, entity_token) = args.entity_token.get_composite_keys();
let query_result = sqlx::query!(
r#"
INSERT INTO favorites
SET
token = ?,
user_token = ?,
entity_type = ?,
entity_token = ?
ON DUPLICATE KEY UPDATE
deleted_at = NULL,
version = version + 1
"#,
&favorite_token,
args.user_token,
entity_type,
entity_token,
)
.execute(args.mysql_executor)
.await;
let _record_id = match query_result {
Ok(res) => res.last_insert_id(),
Err(err) => return Err(anyhow!("Mysql error: {:?}", err)),
};
Ok(favorite_token)
}
@@ -0,0 +1,35 @@
use sqlx::{Executor, MySql};
use errors::AnyhowResult;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::users::UserToken;
// NB: UserToken is only supplied as an optimistic form of validation
pub async fn delete_favorite<'e, 'c, E>(
favorite_token: &'e FavoriteToken,
user_token: &'e UserToken,
mysql_executor: E
)
-> AnyhowResult<()>
where E: 'e + Executor<'c, Database = MySql>
{
sqlx::query!(
r#"
UPDATE favorites
SET
deleted_at = CURRENT_TIMESTAMP,
version = version + 1
WHERE
token = ?
AND user_token = ?
LIMIT 1
"#,
favorite_token,
user_token
)
.execute(mysql_executor)
.await?;
Ok(())
}
@@ -0,0 +1,48 @@
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::tts_models::TtsModelToken;
use tokens::tokens::tts_results::TtsResultToken;
use tokens::tokens::users::UserToken;
use tokens::tokens::voice_conversion_models::VoiceConversionModelToken;
use tokens::tokens::w2l_results::W2lResultToken;
use tokens::tokens::w2l_templates::W2lTemplateToken;
use tokens::tokens::zs_voices::ZsVoiceToken;
pub enum FavoriteEntityToken {
User(UserToken),
TtsModel(TtsModelToken),
TtsResult(TtsResultToken),
W2lTemplate(W2lTemplateToken),
W2lResult(W2lResultToken),
MediaFile(MediaFileToken),
VoiceConversionModel(VoiceConversionModelToken),
ZsVoice(ZsVoiceToken),
}
impl FavoriteEntityToken {
pub fn from_entity_type_and_token(entity_type: FavoriteEntityType, token: &str) -> Self {
match entity_type {
FavoriteEntityType::User => Self::User(UserToken::new_from_str(token)),
FavoriteEntityType::TtsModel => Self::TtsModel(TtsModelToken::new_from_str(token)),
FavoriteEntityType::TtsResult => Self::TtsResult(TtsResultToken::new_from_str(token)),
FavoriteEntityType::W2lTemplate => Self::W2lTemplate(W2lTemplateToken::new_from_str(token)),
FavoriteEntityType::W2lResult => Self::W2lResult(W2lResultToken::new_from_str(token)),
FavoriteEntityType::MediaFile => Self::MediaFile(MediaFileToken::new_from_str(token)),
FavoriteEntityType::VoiceConversionModel => Self::VoiceConversionModel(VoiceConversionModelToken::new_from_str(token)),
FavoriteEntityType::ZsVoice => Self::ZsVoice(ZsVoiceToken::new_from_str(token)),
}
}
pub fn get_composite_keys(&self) -> (FavoriteEntityType, &str) {
match self {
FavoriteEntityToken::User(user_token) => (FavoriteEntityType::User, user_token.as_str()),
FavoriteEntityToken::TtsModel(tts_model_token) => (FavoriteEntityType::TtsModel, tts_model_token.as_str()),
FavoriteEntityToken::TtsResult(tts_result_token) => (FavoriteEntityType::TtsResult, tts_result_token.as_str()),
FavoriteEntityToken::W2lTemplate(w2l_template_token) => (FavoriteEntityType::W2lTemplate, w2l_template_token.as_str()),
FavoriteEntityToken::W2lResult(w2l_result_token) => (FavoriteEntityType::W2lResult, w2l_result_token.as_str()),
FavoriteEntityToken::MediaFile(media_file_token) => (FavoriteEntityType::MediaFile, media_file_token.as_str()),
FavoriteEntityToken::VoiceConversionModel(voice_conversion_model_token) => (FavoriteEntityType::VoiceConversionModel, voice_conversion_model_token.as_str()),
FavoriteEntityToken::ZsVoice(zs_voice_token) => (FavoriteEntityType::ZsVoice, zs_voice_token.as_str()),
}
}
}
@@ -0,0 +1,98 @@
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use sqlx::{Executor, MySql};
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use errors::AnyhowResult;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::users::UserToken;
pub struct Favorite {
pub token: FavoriteToken,
pub entity_type: FavoriteEntityType,
pub entity_token: String,
pub user_token: UserToken,
pub username: String,
pub user_display_name: String,
pub user_gravatar_hash: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub maybe_deleted_at: Option<DateTime<Utc>>,
}
pub async fn get_favorite<'e, 'c, E>(
favorite_token: &'e FavoriteToken,
mysql_executor: E
)
-> AnyhowResult<Option<Favorite>>
where E: 'e + Executor<'c, Database = MySql>
{
let maybe_results = sqlx::query_as!(
RawFavorite,
r#"
SELECT
f.token as `token: tokens::tokens::favorites::FavoriteToken`,
f.entity_type as `entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType`,
f.entity_token,
f.user_token as `user_token: tokens::tokens::users::UserToken`,
u.username,
u.display_name as user_display_name,
u.email_gravatar_hash as user_gravatar_hash,
f.created_at,
f.updated_at,
f.deleted_at
FROM
favorites AS f
JOIN users AS u
ON f.user_token = u.token
WHERE
f.token = ?
"#,
favorite_token
)
.fetch_one(mysql_executor)
.await;
match maybe_results {
Ok(favorite) => Ok(Some(Favorite {
token: favorite.token,
entity_type: favorite.entity_type,
entity_token: favorite.entity_token,
user_token: favorite.user_token,
username: favorite.username,
user_display_name: favorite.user_display_name,
user_gravatar_hash: favorite.user_gravatar_hash,
created_at: favorite.created_at,
updated_at: favorite.updated_at,
maybe_deleted_at: favorite.deleted_at,
})),
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(anyhow!("Error querying for IP ban: {:?}", err)),
}
}
}
pub struct RawFavorite {
pub token: FavoriteToken,
pub entity_type: FavoriteEntityType,
pub entity_token: String,
pub user_token: UserToken,
pub username: String,
pub user_display_name: String,
pub user_gravatar_hash: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
}
@@ -0,0 +1,103 @@
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use log::warn;
use sqlx::MySqlPool;
use errors::AnyhowResult;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::users::UserToken;
use crate::queries::favorites::favorite_entity_token::FavoriteEntityToken;
pub struct Favorite {
pub token: FavoriteToken,
pub user_token: UserToken,
pub username: String,
pub user_display_name: String,
pub user_gravatar_hash: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub maybe_deleted_at: Option<DateTime<Utc>>,
}
pub async fn list_favorites_for_entity(
favorite_entity_token: FavoriteEntityToken,
mysql_pool: &MySqlPool
) -> AnyhowResult<Vec<Favorite>> {
let (entity_type, entity_token) = favorite_entity_token.get_composite_keys();
let maybe_results= sqlx::query_as!(
RawFavoriteRecord,
r#"
SELECT
f.token as `token: tokens::tokens::favorites::FavoriteToken`,
f.user_token as `user_token: tokens::tokens::users::UserToken`,
u.username,
u.display_name as user_display_name,
u.email_gravatar_hash as user_gravatar_hash,
f.created_at,
f.updated_at,
f.deleted_at
FROM
favorites AS f
JOIN users AS u
ON f.user_token = u.token
WHERE
f.entity_type = ?
AND f.entity_token = ?
AND f.deleted_at IS NULL
ORDER BY f.id DESC
LIMIT 50
"#,
entity_type,
entity_token
)
.fetch_all(mysql_pool)
.await;
match maybe_results {
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(Vec::new()),
_ => {
warn!("list favorites db error: {:?}", err);
Err(anyhow!("error with query: {:?}", err))
}
},
Ok(results) => Ok(results.into_iter()
.map(|favorite| favorite.into_public_type())
.collect()),
}
}
pub struct RawFavoriteRecord {
token: FavoriteToken,
user_token: UserToken,
username: String,
user_display_name: String,
user_gravatar_hash: String,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
deleted_at: Option<DateTime<Utc>>,
}
impl RawFavoriteRecord {
pub fn into_public_type(self) -> Favorite {
Favorite {
token: self.token,
user_token: self.user_token,
username: self.username,
user_display_name: self.user_display_name,
user_gravatar_hash: self.user_gravatar_hash,
created_at: self.created_at,
updated_at: self.updated_at,
maybe_deleted_at: self.deleted_at,
}
}
}
@@ -0,0 +1,147 @@
use anyhow::anyhow;
use log::warn;
use sqlx::MySqlPool;
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use errors::AnyhowResult;
use tokens::tokens::users::UserToken;
use crate::queries::favorites::favorite_entity_token::FavoriteEntityToken;
use crate::queries::favorites::list_user_favorites_result::{UserFavorite, RawUserFavoriteRecord};
pub async fn list_user_favorites(
username: &str,
mysql_pool: &MySqlPool
) -> AnyhowResult<Vec<UserFavorite>> {
/// TODO(bt,2023-11-21): Maybe this query can use a switch
/// See: https://stackoverflow.com/questions/2761574/mysql-use-case-else-value-as-join-parameter
let maybe_results= sqlx::query_as!(
RawUserFavoriteRecord,
r#"
SELECT
f.token as `token: tokens::tokens::favorites::FavoriteToken`,
f.entity_type as `entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType`,
f.entity_token,
f.user_token as `user_token: tokens::tokens::users::UserToken`,
u.username,
u.display_name as user_display_name,
u.email_gravatar_hash as user_gravatar_hash,
f.created_at,
f.updated_at,
f.deleted_at,
media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,
media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,
tts_models.title as maybe_descriptive_text_tts_model_title,
tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,
users.display_name as maybe_descriptive_text_user_display_name,
voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,
zs_voices.title as maybe_descriptive_text_zs_voice_title
FROM
favorites AS f
JOIN users AS u
ON f.user_token = u.token
LEFT OUTER JOIN media_files ON media_files.token = f.entity_token
LEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token
LEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token
LEFT OUTER JOIN users ON users.token = f.entity_token
LEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token
LEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token
WHERE
u.username = ?
AND f.deleted_at IS NULL
ORDER BY f.id DESC
LIMIT 50
"#,
username
)
.fetch_all(mysql_pool)
.await;
match maybe_results {
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(Vec::new()),
_ => {
warn!("list favorites db error: {:?}", err);
Err(anyhow!("error with query: {:?}", err))
}
},
Ok(results) => Ok(results.into_iter()
.map(|favorite| favorite.into_public_type())
.collect()),
}
}
pub async fn list_user_favorites_by_entity_type(
username: &str,
entity_type: FavoriteEntityType,
mysql_pool: &MySqlPool
) -> AnyhowResult<Vec<UserFavorite>> {
let maybe_results= sqlx::query_as!(
RawUserFavoriteRecord,
r#"
SELECT
f.token as `token: tokens::tokens::favorites::FavoriteToken`,
f.entity_type as `entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType`,
f.entity_token,
f.user_token as `user_token: tokens::tokens::users::UserToken`,
u.username,
u.display_name as user_display_name,
u.email_gravatar_hash as user_gravatar_hash,
f.created_at,
f.updated_at,
f.deleted_at,
media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,
media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,
tts_models.title as maybe_descriptive_text_tts_model_title,
tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,
users.display_name as maybe_descriptive_text_user_display_name,
voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,
zs_voices.title as maybe_descriptive_text_zs_voice_title
FROM
favorites AS f
JOIN users AS u
ON f.user_token = u.token
LEFT OUTER JOIN media_files ON media_files.token = f.entity_token
LEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token
LEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token
LEFT OUTER JOIN users ON users.token = f.entity_token
LEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token
LEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token
WHERE
u.username = ?
AND f.entity_type = ?
AND f.deleted_at IS NULL
ORDER BY f.id DESC
LIMIT 50
"#,
username,
entity_type
)
.fetch_all(mysql_pool)
.await;
match maybe_results {
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(Vec::new()),
_ => {
warn!("list favorites db error: {:?}", err);
Err(anyhow!("error with query: {:?}", err))
}
},
Ok(results) => Ok(results.into_iter()
.map(|favorite| favorite.into_public_type())
.collect()),
}
}
@@ -0,0 +1,85 @@
use chrono::{DateTime, Utc};
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::users::UserToken;
pub struct UserFavorite {
pub token: FavoriteToken,
pub entity_type: FavoriteEntityType,
pub entity_token: String,
/// Something descriptive about the favorited entity.
/// This might be TTS text, a username, etc. It depends on the entity type.
pub maybe_entity_descriptive_text: Option<String>,
pub user_token: UserToken,
pub username: String,
pub user_display_name: String,
pub user_gravatar_hash: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub maybe_deleted_at: Option<DateTime<Utc>>,
}
pub struct RawUserFavoriteRecord {
pub (crate) token: FavoriteToken,
pub (crate) entity_type: FavoriteEntityType,
pub (crate) entity_token: String,
pub (crate) user_token: UserToken,
pub (crate) username: String,
pub (crate) user_display_name: String,
pub (crate) user_gravatar_hash: String,
pub (crate) created_at: DateTime<Utc>,
pub (crate) updated_at: DateTime<Utc>,
pub (crate) deleted_at: Option<DateTime<Utc>>,
pub (crate) maybe_media_file_type: Option<MediaFileType>,
pub (crate) maybe_media_file_origin_category: Option<MediaFileOriginCategory>,
pub (crate) maybe_descriptive_text_tts_model_title: Option<String>,
pub (crate) maybe_descriptive_text_tts_result_inference_text: Option<String>,
pub (crate) maybe_descriptive_text_user_display_name: Option<String>,
pub (crate) maybe_descriptive_text_voice_conversion_model_title: Option<String>,
pub (crate) maybe_descriptive_text_zs_voice_title: Option<String>,
}
impl RawUserFavoriteRecord {
pub fn into_public_type(self) -> UserFavorite {
UserFavorite {
token: self.token,
entity_type: self.entity_type,
entity_token: self.entity_token,
maybe_entity_descriptive_text: match self.entity_type {
FavoriteEntityType::User => self.maybe_descriptive_text_user_display_name,
FavoriteEntityType::TtsModel => self.maybe_descriptive_text_tts_model_title,
FavoriteEntityType::TtsResult => self.maybe_descriptive_text_tts_result_inference_text,
FavoriteEntityType::W2lTemplate => None,
FavoriteEntityType::W2lResult => None,
// TODO(bt,2023-11-21): Summary text needs to be better enriched.
FavoriteEntityType::MediaFile => self.maybe_media_file_type
.and_then(|media_file_type| match media_file_type {
MediaFileType::Audio => Some("audio media file".to_string()),
MediaFileType::Image => Some("image media file".to_string()),
MediaFileType::Video => Some("video media file".to_string()),
}),
FavoriteEntityType::VoiceConversionModel => self.maybe_descriptive_text_voice_conversion_model_title,
FavoriteEntityType::ZsVoice => self.maybe_descriptive_text_zs_voice_title,
},
user_token: self.user_token,
username: self.username,
user_display_name: self.user_display_name,
user_gravatar_hash: self.user_gravatar_hash,
created_at: self.created_at,
updated_at: self.updated_at,
maybe_deleted_at: self.deleted_at,
}
}
}
@@ -0,0 +1,7 @@
pub mod create_favorite;
pub mod delete_favorite;
pub mod favorite_entity_token;
pub mod get_favorite;
pub mod list_favorites_for_entity;
pub mod list_user_favorites;
pub mod list_user_favorites_result;
+2 -1
View File
@@ -3,6 +3,7 @@ pub mod audit_logs;
pub mod billing;
pub mod comments;
pub mod email_sender_jobs;
pub mod favorites;
pub mod generic_download;
pub mod generic_inference;
pub mod generic_synthetic_ids;
@@ -21,4 +22,4 @@ pub mod vocoder;
pub mod voice_clone_requests;
pub mod voice_conversion;
pub mod voice_designer;
pub mod w2l;
pub mod w2l;
@@ -0,0 +1,127 @@
// NB: Incrementally getting rid of build warnings...
#![forbid(unused_imports)]
#![forbid(unused_mut)]
#![forbid(unused_variables)]
use std::fmt;
use std::sync::Arc;
use actix_web::{HttpRequest, HttpResponse, web};
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use log::warn;
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use mysql_queries::queries::favorites::favorite_entity_token::FavoriteEntityToken;
use mysql_queries::queries::favorites::create_favorite::{create_favorite, CreateFavoriteArgs};
use tokens::tokens::favorites::FavoriteToken;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::server_state::ServerState;
#[derive(Deserialize)]
pub struct CreateFavoriteRequest {
entity_token: String,
entity_type: FavoriteEntityType,
}
#[derive(Serialize)]
pub struct CreateFavoriteSuccessResponse {
pub success: bool,
pub favorite_token: FavoriteToken,
}
#[derive(Debug)]
pub enum CreateFavoriteError {
BadInput(String),
NotAuthorized,
ServerError,
}
impl ResponseError for CreateFavoriteError {
fn status_code(&self) -> StatusCode {
match *self {
CreateFavoriteError::BadInput(_) => StatusCode::BAD_REQUEST,
CreateFavoriteError::NotAuthorized => StatusCode::UNAUTHORIZED,
CreateFavoriteError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let error_reason = match self {
CreateFavoriteError::BadInput(reason) => reason.to_string(),
CreateFavoriteError::NotAuthorized => "unauthorized".to_string(),
CreateFavoriteError::ServerError => "server error".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
}
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for CreateFavoriteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn create_favorite_handler(
http_request: HttpRequest,
request: web::Json<CreateFavoriteRequest>,
server_state: web::Data<Arc<ServerState>>,
) -> Result<HttpResponse, CreateFavoriteError>
{
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await
.map_err(|err| {
warn!("MySql pool error: {:?}", err);
CreateFavoriteError::ServerError
})?;
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
CreateFavoriteError::ServerError
})?;
let user_session = match maybe_user_session {
Some(session) => session,
None => {
warn!("not logged in");
return Err(CreateFavoriteError::NotAuthorized);
}
};
let entity_token = FavoriteEntityToken::from_entity_type_and_token(
request.entity_type, &request.entity_token);
let query_result = create_favorite(CreateFavoriteArgs {
entity_token: &entity_token,
user_token: &user_session.user_token_typed,
mysql_executor: &mut *mysql_connection,
phantom: Default::default(),
}).await;
let favorite_token = match query_result {
Ok(token) => token,
Err(err) => {
warn!("error inserting favorite: {:?}", err);
return Err(CreateFavoriteError::ServerError);
}
};
let response = CreateFavoriteSuccessResponse {
success: true,
favorite_token,
};
let body = serde_json::to_string(&response)
.map_err(|_e| CreateFavoriteError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
@@ -0,0 +1,116 @@
// NB: Incrementally getting rid of build warnings...
#![forbid(unused_imports)]
#![forbid(unused_mut)]
#![forbid(unused_variables)]
use std::fmt;
use std::sync::Arc;
use actix_web::{HttpRequest, HttpResponse, web};
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::Path;
use log::warn;
use mysql_queries::queries::favorites::delete_favorite::delete_favorite;
use tokens::tokens::favorites::FavoriteToken;
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;
/// For the URL PathInfo
#[derive(Deserialize)]
pub struct DeleteFavoritePathInfo {
favorite_token: FavoriteToken,
}
#[derive(Deserialize)]
pub struct DeleteFavoriteRequest {
/// NB: this is only to disambiguate when a user is both a mod and an author.
as_mod: Option<bool>,
}
#[derive(Debug)]
pub enum DeleteFavoriteError {
BadInput(String),
NotAuthorized,
NotFound,
ServerError,
}
impl ResponseError for DeleteFavoriteError {
fn status_code(&self) -> StatusCode {
match *self {
DeleteFavoriteError::BadInput(_) => StatusCode::BAD_REQUEST,
DeleteFavoriteError::NotAuthorized => StatusCode::UNAUTHORIZED,
DeleteFavoriteError::NotFound => StatusCode::NOT_FOUND,
DeleteFavoriteError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let error_reason = match self {
DeleteFavoriteError::BadInput(reason) => reason.to_string(),
DeleteFavoriteError::NotAuthorized => "unauthorized".to_string(),
DeleteFavoriteError::NotFound => "not found".to_string(),
DeleteFavoriteError::ServerError => "server error".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
}
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for DeleteFavoriteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn delete_favorite_handler(
http_request: HttpRequest,
path: Path<DeleteFavoritePathInfo>,
_request: web::Json<DeleteFavoriteRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, DeleteFavoriteError> {
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await
.map_err(|err| {
warn!("MySql pool error: {:?}", err);
DeleteFavoriteError::ServerError
})?;
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
DeleteFavoriteError::ServerError
})?;
let user_session = match maybe_user_session {
Some(session) => session,
None => {
return Err(DeleteFavoriteError::NotAuthorized);
}
};
let query_result = delete_favorite(
&path.favorite_token,
&user_session.user_token_typed,
&mut *mysql_connection
).await;
match query_result {
Ok(_) => {},
Err(err) => {
warn!("Update tts mod approval status DB error: {:?}", err);
return Err(DeleteFavoriteError::ServerError);
}
};
Ok(simple_json_success())
}
@@ -0,0 +1,122 @@
// NB: Incrementally getting rid of build warnings...
#![forbid(unused_imports)]
#![forbid(unused_mut)]
#![forbid(unused_variables)]
use std::fmt;
use std::sync::Arc;
use actix_web::{HttpRequest, HttpResponse, web};
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::Path;
use chrono::{DateTime, Utc};
use log::warn;
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use mysql_queries::queries::favorites::favorite_entity_token::FavoriteEntityToken;
use mysql_queries::queries::favorites::list_favorites_for_entity::list_favorites_for_entity;
use tokens::tokens::favorites::FavoriteToken;
use crate::http_server::common_responses::user_details_lite::{DefaultAvatarInfo, UserDetailsLight};
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::server_state::ServerState;
/// For the URL PathInfo
#[derive(Deserialize)]
pub struct ListFavoritesPathInfo {
entity_type: FavoriteEntityType,
entity_token: String,
}
#[derive(Serialize)]
pub struct ListFavoritesSuccessResponse {
pub success: bool,
pub favorites: Vec<Favorite>,
}
#[derive(Serialize)]
pub struct Favorite {
pub token: FavoriteToken,
pub user: UserDetailsLight,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug)]
pub enum ListFavoritesError {
ServerError,
}
impl ResponseError for ListFavoritesError {
fn status_code(&self) -> StatusCode {
match *self {
ListFavoritesError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let error_reason = match self {
ListFavoritesError::ServerError => "server error".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
}
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for ListFavoritesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn list_favorites_for_entity_handler(
_http_request: HttpRequest,
path: Path<ListFavoritesPathInfo>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, ListFavoritesError>
{
let entity_token = FavoriteEntityToken::from_entity_type_and_token(
path.entity_type, &path.entity_token);
let query_results = list_favorites_for_entity(
entity_token,
&server_state.mysql_pool,
).await;
let favorites = match query_results {
Ok(results) => results,
Err(e) => {
warn!("Query error: {:?}", e);
return Err(ListFavoritesError::ServerError);
}
};
let response = ListFavoritesSuccessResponse {
success: true,
favorites: favorites.into_iter()
.map(|favorite| Favorite {
token: favorite.token,
user: UserDetailsLight {
user_token: favorite.user_token.clone(),
username: favorite.username.to_string(), // NB: Cloned because of ref use for avatar below
display_name: favorite.user_display_name.clone(),
gravatar_hash: favorite.user_gravatar_hash.clone(),
default_avatar: DefaultAvatarInfo::from_username(&favorite.username),
},
created_at: favorite.created_at,
updated_at: favorite.updated_at,
})
.collect(),
};
let body = serde_json::to_string(&response)
.map_err(|_e| ListFavoritesError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
@@ -0,0 +1,141 @@
// NB: Incrementally getting rid of build warnings...
#![forbid(unused_imports)]
#![forbid(unused_mut)]
#![forbid(unused_variables)]
use std::fmt;
use std::sync::Arc;
use actix_web::{HttpRequest, HttpResponse, web};
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::Query;
use chrono::{DateTime, Utc};
use log::warn;
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use mysql_queries::queries::favorites::list_user_favorites::{list_user_favorites, list_user_favorites_by_entity_type};
use tokens::tokens::favorites::FavoriteToken;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::server_state::ServerState;
#[derive(Deserialize)]
pub struct ListFavoritesQueryData {
maybe_scoped_entity_type: Option<FavoriteEntityType>,
}
#[derive(Serialize)]
pub struct ListFavoritesSuccessResponse {
pub success: bool,
pub favorites: Vec<Favorite>,
}
#[derive(Serialize)]
pub struct Favorite {
pub token: FavoriteToken,
pub details: FavoriteDetails,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Serialize)]
pub struct FavoriteDetails {
// TODO: This needs titles or some other summary metadata.
pub entity_type: FavoriteEntityType,
pub entity_token: String,
// TODO: Populate this for TTS
pub maybe_summary_text: Option<String>,
// TODO: Populate this for images, video, etc.
pub maybe_thumbnail_url: Option<String>,
}
#[derive(Debug)]
pub enum ListFavoritesError {
ServerError,
NotAuthorizedError,
}
impl ResponseError for ListFavoritesError {
fn status_code(&self) -> StatusCode {
match *self {
ListFavoritesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
ListFavoritesError::NotAuthorizedError => StatusCode::UNAUTHORIZED,
}
}
fn error_response(&self) -> HttpResponse {
let error_reason = match self {
ListFavoritesError::ServerError => "server error".to_string(),
ListFavoritesError::NotAuthorizedError => "not authorized".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
}
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for ListFavoritesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn list_favorites_for_session_handler(
http_request: HttpRequest,
query: Query<ListFavoritesQueryData>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, ListFavoritesError>
{
let 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);
ListFavoritesError::ServerError
})?
.ok_or(ListFavoritesError::NotAuthorizedError)?;
let query_results = match query.maybe_scoped_entity_type {
None => list_user_favorites(&user_session.username, &server_state.mysql_pool).await,
Some(entity_type) =>
list_user_favorites_by_entity_type(&user_session.username, entity_type, &server_state.mysql_pool)
.await,
};
let favorites = match query_results {
Ok(results) => results,
Err(e) => {
warn!("Query error: {:?}", e);
return Err(ListFavoritesError::ServerError);
}
};
let response = ListFavoritesSuccessResponse {
success: true,
favorites: favorites.into_iter()
.map(|favorite| Favorite {
token: favorite.token,
details: FavoriteDetails {
entity_type: favorite.entity_type,
entity_token: favorite.entity_token,
maybe_summary_text: None,
maybe_thumbnail_url: None,
},
created_at: favorite.created_at,
updated_at: favorite.updated_at,
})
.collect(),
};
let body = serde_json::to_string(&response)
.map_err(|_e| ListFavoritesError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
@@ -0,0 +1,138 @@
// NB: Incrementally getting rid of build warnings...
#![forbid(unused_imports)]
#![forbid(unused_mut)]
#![forbid(unused_variables)]
use std::fmt;
use std::sync::Arc;
use actix_web::{HttpRequest, HttpResponse, web};
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::{Path, Query};
use chrono::{DateTime, Utc};
use log::warn;
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use mysql_queries::queries::favorites::list_user_favorites::{list_user_favorites, list_user_favorites_by_entity_type};
use tokens::tokens::favorites::FavoriteToken;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::server_state::ServerState;
#[derive(Deserialize)]
pub struct ListFavoritesPathInfo {
username: String,
}
#[derive(Deserialize)]
pub struct ListFavoritesQueryData {
maybe_scoped_entity_type: Option<FavoriteEntityType>,
}
#[derive(Serialize)]
pub struct ListFavoritesSuccessResponse {
pub success: bool,
pub favorites: Vec<Favorite>,
}
#[derive(Serialize)]
pub struct Favorite {
pub token: FavoriteToken,
pub details: FavoriteDetails,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Serialize)]
pub struct FavoriteDetails {
// TODO: This needs titles or some other summary metadata.
pub entity_type: FavoriteEntityType,
pub entity_token: String,
// TODO: Populate this for TTS
pub maybe_summary_text: Option<String>,
// TODO: Populate this for images, video, etc.
pub maybe_thumbnail_url: Option<String>,
}
#[derive(Debug)]
pub enum ListFavoritesError {
ServerError,
}
impl ResponseError for ListFavoritesError {
fn status_code(&self) -> StatusCode {
match *self {
ListFavoritesError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let error_reason = match self {
ListFavoritesError::ServerError => "server error".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
}
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for ListFavoritesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn list_favorites_for_user_handler(
_http_request: HttpRequest,
path: Path<ListFavoritesPathInfo>,
query: Query<ListFavoritesQueryData>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, ListFavoritesError>
{
let query_results = match query.maybe_scoped_entity_type {
None => list_user_favorites(&path.username, &server_state.mysql_pool).await,
Some(entity_type) =>
list_user_favorites_by_entity_type(&path.username, entity_type, &server_state.mysql_pool)
.await,
};
let favorites = match query_results {
Ok(results) => results,
Err(e) => {
warn!("Query error: {:?}", e);
return Err(ListFavoritesError::ServerError);
}
};
let response = ListFavoritesSuccessResponse {
success: true,
favorites: favorites.into_iter()
.map(|favorite| Favorite {
token: favorite.token,
details: FavoriteDetails {
entity_type: favorite.entity_type,
entity_token: favorite.entity_token,
maybe_summary_text: favorite.maybe_entity_descriptive_text,
// TODO(bt,2023-11-21): Thumbnails need proper support. We should build them as a
// first-class system before handling the backfill here.
maybe_thumbnail_url: None,
},
created_at: favorite.created_at,
updated_at: favorite.updated_at,
})
.collect(),
};
let body = serde_json::to_string(&response)
.map_err(|_e| ListFavoritesError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
@@ -0,0 +1,5 @@
pub mod create_favorite_handler;
pub mod delete_favorite_handler;
pub mod list_favorites_for_entity_handler;
pub mod list_favorites_for_session_handler;
pub mod list_favorites_for_user_handler;
@@ -25,3 +25,4 @@ pub mod voice_clone_requests;
pub mod voice_conversion;
pub mod voice_designer;
pub mod w2l;
pub mod favorites;
@@ -1,9 +1,9 @@
{
"info": {
"_postman_id": "febcb082-e71c-46c4-a172-adf736e01298",
"_postman_id": "5b9aab26-f591-4264-b75b-3075dbeaf6df",
"name": "Base Service Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "30159615"
"_exporter_id": "10633600"
},
"item": [
{
@@ -84,6 +84,77 @@
}
]
},
{
"name": "Favorites",
"item": [
{
"name": "List Favorites",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/v1/favorites/list/user/U:0XTEZ7HX29B9C",
"host": [
"{{baseUrl}}"
],
"path": [
"v1",
"favorites",
"list",
"user",
"U:0XTEZ7HX29B9C"
]
}
},
"response": []
},
{
"name": "Create Favorite",
"request": {
"method": "POST",
"header": [],
"url": {
"raw": "{{baseUrl}}/v1/comments/new",
"host": [
"{{baseUrl}}"
],
"path": [
"v1",
"comments",
"new"
]
}
},
"response": []
},
{
"name": "Delete Favorite",
"request": {
"method": "POST",
"header": [],
"url": {
"raw": "{{baseUrl}}/v1/favorites/delete/:favorite_token",
"host": [
"{{baseUrl}}"
],
"path": [
"v1",
"favorites",
"delete",
":favorite_token"
],
"variable": [
{
"key": "favorite_token",
"value": ""
}
]
}
},
"response": []
}
]
},
{
"name": "Animations",
"item": [
@@ -3,7 +3,6 @@ use actix_service::ServiceFactory;
use actix_web::{App, HttpResponse, web};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::error::Error;
use log::warn;
use actix_helpers::route_builder::RouteBuilder;
use billing_component::default_routes::add_suggested_stripe_billing_routes;
@@ -29,6 +28,11 @@ use crate::http_server::endpoints::comments::list_comments_handler::list_comment
use crate::http_server::endpoints::download_job::enqueue_generic_download::enqueue_generic_download_handler;
use crate::http_server::endpoints::download_job::get_generic_upload_job_status::get_generic_download_job_status_handler;
use crate::http_server::endpoints::events::list_events::list_events_handler;
use crate::http_server::endpoints::favorites::create_favorite_handler::create_favorite_handler;
use crate::http_server::endpoints::favorites::delete_favorite_handler::delete_favorite_handler;
use crate::http_server::endpoints::favorites::list_favorites_for_entity_handler::list_favorites_for_entity_handler;
use crate::http_server::endpoints::favorites::list_favorites_for_session_handler::list_favorites_for_session_handler;
use crate::http_server::endpoints::favorites::list_favorites_for_user_handler::list_favorites_for_user_handler;
use crate::http_server::endpoints::flags::design_refresh_flag::disable_design_refresh_flag_handler::disable_design_refresh_flag_handler;
use crate::http_server::endpoints::flags::design_refresh_flag::enable_design_refresh_flag_handler::enable_design_refresh_flag_handler;
use crate::http_server::endpoints::inference_job::get_inference_job_status::get_inference_job_status_handler;
@@ -186,6 +190,16 @@ pub fn add_routes<T, B> (app: App<T>, server_environment: ServerEnvironment) ->
.add_post("/v1/comments/delete/{comment_token}", delete_comment_handler)
.into_app();
// ==================== Favorites ====================
let mut app = RouteBuilder::from_app(app)
.add_post("/v1/favorites/create", create_favorite_handler)
.add_post("/v1/favorites/delete/{favorite_token}", delete_favorite_handler)
.add_get("/v1/favorites/list/session", list_favorites_for_session_handler)
.add_get("/v1/favorites/list/user/{username}", list_favorites_for_user_handler)
.add_get("/v1/favorites/list/entity/{entity_type}/{entity_token}", list_favorites_for_entity_handler)
.into_app();
// ==================== Animations ====================
let mut app = RouteBuilder::from_app(app)