Merge pull request #198 from storytold/dec-07-mithoo-bookmarks

Rename favorites -> user_bookmarks, add new table
This commit is contained in:
Brandon Thomas authored and GitHub committed 2023-12-11 23:36:22 -05:00
commit d49b01930e
39 files changed
+661 -608

No files matched your search

@@ -1,11 +1,11 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n f.token as `token: tokens::tokens::favorites::FavoriteToken`,\n\n f.entity_type as `entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType`,\n f.entity_token,\n\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n\n f.created_at,\n f.updated_at,\n f.deleted_at\n\nFROM\n favorites AS f\nJOIN users AS u\n ON f.user_token = u.token\nWHERE\n f.token = ?\n ",
"query": "\nSELECT\n f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,\n\n f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,\n f.entity_token,\n\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n\n f.created_at,\n f.updated_at,\n f.deleted_at\n\nFROM\n user_bookmarks AS f\nJOIN users AS u\n ON f.user_token = u.token\nWHERE\n f.token = ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::favorites::FavoriteToken",
"name": "token: tokens::tokens::user_bookmarks::UserBookmarkToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
@@ -15,7 +15,7 @@
},
{
"ordinal": 1,
"name": "entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType",
"name": "entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
@@ -120,5 +120,5 @@
true
]
},
"hash": "f1ef63978793e7da5bd5e1a486254dd436db8021c8f049efa41f43cf7ce75f1d"
"hash": "02136088e16742de2154cd9e84eeebbdb76045ad26aeb1c517a54c296bf31087"
}
@@ -1,11 +1,11 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n f.token as `token: tokens::tokens::favorites::FavoriteToken`,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n\n f.created_at,\n f.updated_at,\n f.deleted_at\n\nFROM\n favorites AS f\nJOIN users AS u\n ON f.user_token = u.token\nWHERE\n f.entity_type = ?\n AND f.entity_token = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"query": "\nSELECT\n f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n\n f.created_at,\n f.updated_at,\n f.deleted_at\n\nFROM\n user_bookmarks AS f\nJOIN users AS u\n ON f.user_token = u.token\nWHERE\n f.entity_type = ?\n AND f.entity_token = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::favorites::FavoriteToken",
"name": "token: tokens::tokens::user_bookmarks::UserBookmarkToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
@@ -98,5 +98,5 @@
true
]
},
"hash": "99cff649bf75f5b05330e7db8536ecc832affbae7c9710e158099079609e5e16"
"hash": "1c1ef2195e81362d0eb023ede236c6fa65ede80d9efcaa4996694ce32c12cc0c"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nUPDATE user_bookmarks\nSET\ndeleted_at = CURRENT_TIMESTAMP,\nversion = version + 1\nWHERE\ntoken = ?\nAND user_token = ?\nLIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "576e5a72e385d44a1e53dc473dd0b5a04e33d7c6b9c38bef96f275b064a12386"
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nUPDATE favorites\nSET\ndeleted_at = CURRENT_TIMESTAMP,\nversion = version + 1\nWHERE\ntoken = ?\nAND user_token = ?\nLIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "6a9fc0a3d983eb1d2bdf7d4c80904d73b9f4a649098fc0ba78cbc20cfde12437"
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO favorites\nSET\n token = ?,\n user_token = ?,\n entity_type = ?,\n entity_token = ?\n\nON DUPLICATE KEY UPDATE\n deleted_at = NULL,\n version = version + 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "72eaa9124662acf0ed354b868fcdfe33c94b61046c30940f55cdc73789f94041"
}
@@ -1,11 +1,11 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n f.token as `token: tokens::tokens::favorites::FavoriteToken`,\n f.entity_type as `entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType`,\n f.entity_token,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n f.created_at,\n f.updated_at,\n f.deleted_at,\n\n media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,\n\n tts_models.title as maybe_descriptive_text_tts_model_title,\n tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,\n users.display_name as maybe_descriptive_text_user_display_name,\n voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,\n zs_voices.title as maybe_descriptive_text_zs_voice_title\n\nFROM\n favorites AS f\nJOIN users AS u\n ON f.user_token = u.token\n\nLEFT OUTER JOIN media_files ON media_files.token = f.entity_token\nLEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token\nLEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token\nLEFT OUTER JOIN users ON users.token = f.entity_token\nLEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token\nLEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token\n\nWHERE\n u.username = ?\n AND f.entity_type = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"query": "\nSELECT\n f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,\n f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,\n f.entity_token,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n f.created_at,\n f.updated_at,\n f.deleted_at,\n\n media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,\n\n tts_models.title as maybe_descriptive_text_tts_model_title,\n tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,\n users.display_name as maybe_descriptive_text_user_display_name,\n voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,\n zs_voices.title as maybe_descriptive_text_zs_voice_title\n\nFROM\n user_bookmarks AS f\nJOIN users AS u\n ON f.user_token = u.token\n\nLEFT OUTER JOIN media_files ON media_files.token = f.entity_token\nLEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token\nLEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token\nLEFT OUTER JOIN users ON users.token = f.entity_token\nLEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token\nLEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token\n\nWHERE\n u.username = ?\n AND f.entity_type = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::favorites::FavoriteToken",
"name": "token: tokens::tokens::user_bookmarks::UserBookmarkToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
@@ -15,7 +15,7 @@
},
{
"ordinal": 1,
"name": "entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType",
"name": "entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
@@ -197,5 +197,5 @@
true
]
},
"hash": "4214d8e86b81eb0138a34702ce12960ee007ca602a7bd64b68924515f8fbb038"
"hash": "d9c00b3c78ed6ec31b281c7ea5fb1efd0cfa6b20899151f9e7f1a122c1f7a70e"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO user_bookmarks\nSET\n token = ?,\n user_token = ?,\n entity_type = ?,\n entity_token = ?\n\nON DUPLICATE KEY UPDATE\n deleted_at = NULL,\n version = version + 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "e8f3bb68896d316498b52fdaaf4c352624b79bd4e097f57db5ad59f53fd66995"
}
@@ -1,11 +1,11 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n f.token as `token: tokens::tokens::favorites::FavoriteToken`,\n f.entity_type as `entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType`,\n f.entity_token,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n f.created_at,\n f.updated_at,\n f.deleted_at,\n\n media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,\n\n tts_models.title as maybe_descriptive_text_tts_model_title,\n tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,\n users.display_name as maybe_descriptive_text_user_display_name,\n voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,\n zs_voices.title as maybe_descriptive_text_zs_voice_title\n\nFROM\n favorites AS f\nJOIN users AS u\n ON f.user_token = u.token\n\nLEFT OUTER JOIN media_files ON media_files.token = f.entity_token\nLEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token\nLEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token\nLEFT OUTER JOIN users ON users.token = f.entity_token\nLEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token\nLEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token\n\nWHERE\n u.username = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"query": "\nSELECT\n f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,\n f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,\n f.entity_token,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n f.created_at,\n f.updated_at,\n f.deleted_at,\n\n media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,\n\n tts_models.title as maybe_descriptive_text_tts_model_title,\n tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,\n users.display_name as maybe_descriptive_text_user_display_name,\n voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,\n zs_voices.title as maybe_descriptive_text_zs_voice_title\n\nFROM\n user_bookmarks AS f\nJOIN users AS u\n ON f.user_token = u.token\n\nLEFT OUTER JOIN media_files ON media_files.token = f.entity_token\nLEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token\nLEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token\nLEFT OUTER JOIN users ON users.token = f.entity_token\nLEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token\nLEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token\n\nWHERE\n u.username = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::favorites::FavoriteToken",
"name": "token: tokens::tokens::user_bookmarks::UserBookmarkToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
@@ -15,7 +15,7 @@
},
{
"ordinal": 1,
"name": "entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType",
"name": "entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
@@ -197,5 +197,5 @@
true
]
},
"hash": "c33f8df5a9ed1991366d6078460d9e936a5470d1794ea319d6cf19b0f5338fd4"
"hash": "eea9531d5261833b93baadee8b99449362c074fc2397e23ecd175b6a0425b16b"
}
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE user_bookmarks;
@@ -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 user_bookmarks
(
-- 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;
@@ -1,128 +0,0 @@
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());
}
}
}
@@ -1 +0,0 @@
pub mod favorite_entity_type;
+1 -1
View File
@@ -10,6 +10,7 @@ pub mod media_uploads;
pub mod model_categories;
pub mod trending_model_analytics;
pub mod tts_models;
pub mod user_bookmarks;
pub mod user_ratings;
pub mod voice_conversion_models;
pub mod voice_conversion_results;
@@ -19,4 +20,3 @@ pub mod model_weights;
pub mod tts_render_tasks;
pub mod web_rendition_targets;
pub mod web_scraping_targets;
pub mod favorites;
@@ -0,0 +1 @@
pub mod user_bookmark_entity_type;
@@ -0,0 +1,128 @@
use serde::Deserialize;
use serde::Serialize;
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
/// Used in the `user_bookmarks` 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 UserBookmarkEntityType {
/// 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!(UserBookmarkEntityType);
impl_mysql_enum_coders!(UserBookmarkEntityType);
/// NB: Legacy API for older code.
impl UserBookmarkEntityType {
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::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
use crate::test_helpers::assert_serialization;
mod serde {
use super::*;
#[test]
fn test_serialization() {
assert_serialization(UserBookmarkEntityType::User, "user");
assert_serialization(UserBookmarkEntityType::TtsModel, "tts_model");
assert_serialization(UserBookmarkEntityType::TtsResult, "tts_result");
assert_serialization(UserBookmarkEntityType::W2lTemplate, "w2l_template");
assert_serialization(UserBookmarkEntityType::W2lResult, "w2l_result");
assert_serialization(UserBookmarkEntityType::MediaFile, "media_file");
assert_serialization(UserBookmarkEntityType::VoiceConversionModel, "voice_conversion_model");
assert_serialization(UserBookmarkEntityType::ZsVoice, "zs_voice");
}
}
mod impl_methods {
use super::*;
#[test]
fn test_to_str() {
assert_eq!(UserBookmarkEntityType::User.to_str(), "user");
assert_eq!(UserBookmarkEntityType::TtsModel.to_str(), "tts_model");
assert_eq!(UserBookmarkEntityType::TtsResult.to_str(), "tts_result");
assert_eq!(UserBookmarkEntityType::W2lTemplate.to_str(), "w2l_template");
assert_eq!(UserBookmarkEntityType::W2lResult.to_str(), "w2l_result");
assert_eq!(UserBookmarkEntityType::MediaFile.to_str(), "media_file");
assert_eq!(UserBookmarkEntityType::VoiceConversionModel.to_str(), "voice_conversion_model");
assert_eq!(UserBookmarkEntityType::ZsVoice.to_str(), "zs_voice");
}
#[test]
fn test_from_str() {
assert_eq!(UserBookmarkEntityType::from_str("user").unwrap(), UserBookmarkEntityType::User);
assert_eq!(UserBookmarkEntityType::from_str("tts_model").unwrap(), UserBookmarkEntityType::TtsModel);
assert_eq!(UserBookmarkEntityType::from_str("tts_result").unwrap(), UserBookmarkEntityType::TtsResult);
assert_eq!(UserBookmarkEntityType::from_str("w2l_template").unwrap(), UserBookmarkEntityType::W2lTemplate);
assert_eq!(UserBookmarkEntityType::from_str("w2l_result").unwrap(), UserBookmarkEntityType::W2lResult);
assert_eq!(UserBookmarkEntityType::from_str("media_file").unwrap(), UserBookmarkEntityType::MediaFile);
assert_eq!(UserBookmarkEntityType::from_str("voice_conversion_model").unwrap(), UserBookmarkEntityType::VoiceConversionModel);
assert_eq!(UserBookmarkEntityType::from_str("zs_voice").unwrap(), UserBookmarkEntityType::ZsVoice);
assert!(UserBookmarkEntityType::from_str("foo").is_err());
}
}
}
+2 -2
View File
@@ -27,7 +27,6 @@ pub(crate) enum TokenPrefix {
Comment,
DownloadJob,
EmailSenderJob,
Favorite,
InferenceJob,
MediaFile,
MediaUpload,
@@ -36,6 +35,7 @@ pub(crate) enum TokenPrefix {
PasswordReset,
TtsRenderTask, // NB: aichatbot / sqlite
User,
UserBookmark,
UserSession,
VoiceConversionModel,
VoiceConversionResult,
@@ -94,7 +94,6 @@ 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_",
@@ -102,6 +101,7 @@ impl PrefixGenerator for TokenPrefix {
Self::PasswordReset => "pw_reset_",
Self::TtsRenderTask => "tts_task_",
Self::User => "user_", // NB: Previously "U:"
Self::UserBookmark => "ub_",
Self::UserSession => "session_",
Self::VoiceConversionModel => "vcm_",
Self::VoiceConversionResult => "vcr_",
+1 -1
View File
@@ -34,4 +34,4 @@ pub mod w2l_templates;
pub mod zs_voice_dataset_samples;
pub mod zs_voice_datasets;
pub mod zs_voices;
pub mod favorites;
pub mod user_bookmarks;
@@ -8,7 +8,7 @@ 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);
pub struct UserBookmarkToken(pub String);
impl_string_token!(FavoriteToken);
impl_crockford_generator!(FavoriteToken, 32usize, TokenPrefix::Favorite, CrockfordLower);
impl_string_token!(UserBookmarkToken);
impl_crockford_generator!(UserBookmarkToken, 32usize, TokenPrefix::UserBookmark, CrockfordLower);
@@ -1,48 +0,0 @@
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()),
}
}
}
@@ -1,7 +0,0 @@
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;
+1 -1
View File
@@ -3,7 +3,6 @@ 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;
@@ -18,6 +17,7 @@ pub mod trending_model_analytics;
pub mod tts;
pub mod twitch;
pub mod users;
pub mod user_bookmarks;
pub mod vocoder;
pub mod voice_clone_requests;
pub mod voice_conversion;
@@ -4,15 +4,15 @@ use anyhow::anyhow;
use sqlx::{Executor, MySql};
use errors::AnyhowResult;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::user_bookmarks::UserBookmarkToken;
use tokens::tokens::users::UserToken;
use crate::queries::favorites::favorite_entity_token::FavoriteEntityToken;
use crate::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
pub struct CreateFavoriteArgs<'e, 'c, E>
pub struct CreateUserBookmarkArgs<'e, 'c, E>
where E: 'e + Executor<'c, Database = MySql>
{
pub entity_token: &'e FavoriteEntityToken,
pub entity_token: &'e UserBookmarkEntityToken,
pub user_token: &'e UserToken,
@@ -23,19 +23,19 @@ pub struct CreateFavoriteArgs<'e, 'c, E>
pub phantom: PhantomData<&'c E>,
}
pub async fn create_favorite<'e, 'c : 'e, E>(
args: CreateFavoriteArgs<'e, 'c, E>,
pub async fn create_user_bookmark<'e, 'c : 'e, E>(
args: CreateUserBookmarkArgs<'e, 'c, E>,
)
-> AnyhowResult<FavoriteToken>
-> AnyhowResult<UserBookmarkToken>
where E: 'e + Executor<'c, Database = MySql>
{
let favorite_token = FavoriteToken::generate();
let user_bookmark_token = UserBookmarkToken::generate();
let (entity_type, entity_token) = args.entity_token.get_composite_keys();
let query_result = sqlx::query!(
r#"
INSERT INTO favorites
INSERT INTO user_bookmarks
SET
token = ?,
user_token = ?,
@@ -46,7 +46,7 @@ ON DUPLICATE KEY UPDATE
deleted_at = NULL,
version = version + 1
"#,
&favorite_token,
&user_bookmark_token,
args.user_token,
entity_type,
entity_token,
@@ -59,5 +59,5 @@ ON DUPLICATE KEY UPDATE
Err(err) => return Err(anyhow!("Mysql error: {:?}", err)),
};
Ok(favorite_token)
Ok(user_bookmark_token)
}
@@ -1,22 +1,22 @@
use sqlx::{Executor, MySql};
use errors::AnyhowResult;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::user_bookmarks::UserBookmarkToken;
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
pub async fn delete_user_bookmark<'e, 'c, E>(
user_bookmark_token: &'e UserBookmarkToken,
user_token: &'e UserToken,
mysql_executor: E
)
-> AnyhowResult<()>
-> AnyhowResult<()>
where E: 'e + Executor<'c, Database = MySql>
{
sqlx::query!(
r#"
UPDATE favorites
UPDATE user_bookmarks
SET
deleted_at = CURRENT_TIMESTAMP,
version = version + 1
@@ -25,7 +25,7 @@ token = ?
AND user_token = ?
LIMIT 1
"#,
favorite_token,
user_bookmark_token,
user_token
)
.execute(mysql_executor)
@@ -2,15 +2,15 @@ use anyhow::anyhow;
use chrono::{DateTime, Utc};
use sqlx::{Executor, MySql};
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
use errors::AnyhowResult;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::user_bookmarks::UserBookmarkToken;
use tokens::tokens::users::UserToken;
pub struct Favorite {
pub token: FavoriteToken,
pub struct UserBookmark {
pub token: UserBookmarkToken,
pub entity_type: FavoriteEntityType,
pub entity_type: UserBookmarkEntityType,
pub entity_token: String,
pub user_token: UserToken,
@@ -23,21 +23,21 @@ pub struct Favorite {
pub maybe_deleted_at: Option<DateTime<Utc>>,
}
pub async fn get_favorite<'e, 'c, E>(
favorite_token: &'e FavoriteToken,
mysql_executor: E
pub async fn get_user_bookmark<'e, 'c, E>(
user_bookmark_token: &'e UserBookmarkToken,
mysql_executor: E
)
-> AnyhowResult<Option<Favorite>>
-> AnyhowResult<Option<UserBookmark>>
where E: 'e + Executor<'c, Database = MySql>
{
let maybe_results = sqlx::query_as!(
RawFavorite,
RawUserBookmark,
r#"
SELECT
f.token as `token: tokens::tokens::favorites::FavoriteToken`,
f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,
f.entity_type as `entity_type: enums::by_table::favorites::favorite_entity_type::FavoriteEntityType`,
f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,
f.entity_token,
f.user_token as `user_token: tokens::tokens::users::UserToken`,
@@ -50,29 +50,29 @@ SELECT
f.deleted_at
FROM
favorites AS f
user_bookmarks AS f
JOIN users AS u
ON f.user_token = u.token
WHERE
f.token = ?
"#,
favorite_token
user_bookmark_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,
Ok(user_bookmark) => Ok(Some(UserBookmark {
token: user_bookmark.token,
entity_type: user_bookmark.entity_type,
entity_token: user_bookmark.entity_token,
user_token: user_bookmark.user_token,
username: user_bookmark.username,
user_display_name: user_bookmark.user_display_name,
user_gravatar_hash: user_bookmark.user_gravatar_hash,
created_at: user_bookmark.created_at,
updated_at: user_bookmark.updated_at,
maybe_deleted_at: user_bookmark.deleted_at,
})),
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(None),
@@ -81,10 +81,10 @@ WHERE
}
}
pub struct RawFavorite {
pub token: FavoriteToken,
pub struct RawUserBookmark {
pub token: UserBookmarkToken,
pub entity_type: FavoriteEntityType,
pub entity_type: UserBookmarkEntityType,
pub entity_token: String,
pub user_token: UserToken,
@@ -2,24 +2,26 @@ use anyhow::anyhow;
use log::warn;
use sqlx::MySqlPool;
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
use errors::AnyhowResult;
use crate::queries::user_bookmarks::list_user_bookmarks_result::UserBookmark;
use crate::queries::user_bookmarks::list_user_bookmarks_result::RawUserBookmarkRecord;
use tokens::tokens::user_bookmarks::UserBookmarkToken;
use crate::queries::favorites::list_user_favorites_result::{RawUserFavoriteRecord, UserFavorite};
pub async fn list_user_favorites(
pub async fn list_user_bookmarks(
username: &str,
mysql_pool: &MySqlPool
) -> AnyhowResult<Vec<UserFavorite>> {
) -> AnyhowResult<Vec<UserBookmark>> {
/// 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,
RawUserBookmarkRecord,
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.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,
f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,
f.entity_token,
f.user_token as `user_token: tokens::tokens::users::UserToken`,
u.username,
@@ -39,7 +41,7 @@ SELECT
zs_voices.title as maybe_descriptive_text_zs_voice_title
FROM
favorites AS f
user_bookmarks AS f
JOIN users AS u
ON f.user_token = u.token
@@ -65,28 +67,28 @@ LIMIT 50
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(Vec::new()),
_ => {
warn!("list favorites db error: {:?}", err);
warn!("list user_bookmarks db error: {:?}", err);
Err(anyhow!("error with query: {:?}", err))
}
},
Ok(results) => Ok(results.into_iter()
.map(|favorite| favorite.into_public_type())
.map(|user_bookmark| user_bookmark.into_public_type())
.collect()),
}
}
pub async fn list_user_favorites_by_entity_type(
username: &str,
entity_type: FavoriteEntityType,
mysql_pool: &MySqlPool
) -> AnyhowResult<Vec<UserFavorite>> {
pub async fn list_user_user_bookmarks_by_entity_type(
username: &str,
entity_type: UserBookmarkEntityType,
mysql_pool: &MySqlPool
) -> AnyhowResult<Vec<UserBookmark>> {
let maybe_results= sqlx::query_as!(
RawUserFavoriteRecord,
RawUserBookmarkRecord,
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.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,
f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,
f.entity_token,
f.user_token as `user_token: tokens::tokens::users::UserToken`,
u.username,
@@ -106,7 +108,7 @@ SELECT
zs_voices.title as maybe_descriptive_text_zs_voice_title
FROM
favorites AS f
user_bookmarks AS f
JOIN users AS u
ON f.user_token = u.token
@@ -134,12 +136,12 @@ LIMIT 50
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(Vec::new()),
_ => {
warn!("list favorites db error: {:?}", err);
warn!("list user_bookmarks db error: {:?}", err);
Err(anyhow!("error with query: {:?}", err))
}
},
Ok(results) => Ok(results.into_iter()
.map(|favorite| favorite.into_public_type())
.map(|user_bookmark| user_bookmark.into_public_type())
.collect()),
}
}
@@ -4,13 +4,13 @@ use log::warn;
use sqlx::MySqlPool;
use errors::AnyhowResult;
use tokens::tokens::favorites::FavoriteToken;
use tokens::tokens::user_bookmarks::UserBookmarkToken;
use tokens::tokens::users::UserToken;
use crate::queries::favorites::favorite_entity_token::FavoriteEntityToken;
use crate::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
pub struct Favorite {
pub token: FavoriteToken,
pub struct UserBookmark {
pub token: UserBookmarkToken,
pub user_token: UserToken,
pub username: String,
@@ -22,18 +22,18 @@ pub struct Favorite {
pub maybe_deleted_at: Option<DateTime<Utc>>,
}
pub async fn list_favorites_for_entity(
favorite_entity_token: FavoriteEntityToken,
pub async fn list_user_bookmarks_for_entity(
user_bookmark_entity_token: UserBookmarkEntityToken,
mysql_pool: &MySqlPool
) -> AnyhowResult<Vec<Favorite>> {
) -> AnyhowResult<Vec<UserBookmark>> {
let (entity_type, entity_token) = favorite_entity_token.get_composite_keys();
let (entity_type, entity_token) = user_bookmark_entity_token.get_composite_keys();
let maybe_results= sqlx::query_as!(
RawFavoriteRecord,
RawUserBookmarkRecord,
r#"
SELECT
f.token as `token: tokens::tokens::favorites::FavoriteToken`,
f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,
f.user_token as `user_token: tokens::tokens::users::UserToken`,
u.username,
u.display_name as user_display_name,
@@ -44,7 +44,7 @@ SELECT
f.deleted_at
FROM
favorites AS f
user_bookmarks AS f
JOIN users AS u
ON f.user_token = u.token
WHERE
@@ -64,18 +64,18 @@ LIMIT 50
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(Vec::new()),
_ => {
warn!("list favorites db error: {:?}", err);
warn!("list user_bookmarks db error: {:?}", err);
Err(anyhow!("error with query: {:?}", err))
}
},
Ok(results) => Ok(results.into_iter()
.map(|favorite| favorite.into_public_type())
.map(|user_bookmark| user_bookmark.into_public_type())
.collect()),
}
}
pub struct RawFavoriteRecord {
token: FavoriteToken,
pub struct RawUserBookmarkRecord {
token: UserBookmarkToken,
user_token: UserToken,
username: String,
@@ -87,9 +87,9 @@ pub struct RawFavoriteRecord {
deleted_at: Option<DateTime<Utc>>,
}
impl RawFavoriteRecord {
pub fn into_public_type(self) -> Favorite {
Favorite {
impl RawUserBookmarkRecord {
pub fn into_public_type(self) -> UserBookmark {
UserBookmark {
token: self.token,
user_token: self.user_token,
username: self.username,
@@ -1,18 +1,18 @@
use chrono::{DateTime, Utc};
use enums::by_table::favorites::favorite_entity_type::FavoriteEntityType;
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
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::user_bookmarks::UserBookmarkToken;
use tokens::tokens::users::UserToken;
pub struct UserFavorite {
pub token: FavoriteToken,
pub struct UserBookmark {
pub token: UserBookmarkToken,
pub entity_type: FavoriteEntityType,
pub entity_type: UserBookmarkEntityType,
pub entity_token: String,
/// Something descriptive about the favorited entity.
/// Something descriptive about the bookmarked entity.
/// This might be TTS text, a username, etc. It depends on the entity type.
pub maybe_entity_descriptive_text: Option<String>,
@@ -26,10 +26,10 @@ pub struct UserFavorite {
pub maybe_deleted_at: Option<DateTime<Utc>>,
}
pub struct RawUserFavoriteRecord {
pub (crate) token: FavoriteToken,
pub struct RawUserBookmarkRecord {
pub (crate) token: UserBookmarkToken,
pub (crate) entity_type: FavoriteEntityType,
pub (crate) entity_type: UserBookmarkEntityType,
pub (crate) entity_token: String,
pub (crate) user_token: UserToken,
@@ -51,27 +51,27 @@ pub struct RawUserFavoriteRecord {
pub (crate) maybe_descriptive_text_zs_voice_title: Option<String>,
}
impl RawUserFavoriteRecord {
pub fn into_public_type(self) -> UserFavorite {
UserFavorite {
impl RawUserBookmarkRecord {
pub fn into_public_type(self) -> UserBookmark {
UserBookmark {
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,
UserBookmarkEntityType::User => self.maybe_descriptive_text_user_display_name,
UserBookmarkEntityType::TtsModel => self.maybe_descriptive_text_tts_model_title,
UserBookmarkEntityType::TtsResult => self.maybe_descriptive_text_tts_result_inference_text,
UserBookmarkEntityType::W2lTemplate => None,
UserBookmarkEntityType::W2lResult => None,
// TODO(bt,2023-11-21): Summary text needs to be better enriched.
FavoriteEntityType::MediaFile => self.maybe_media_file_type
UserBookmarkEntityType::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,
UserBookmarkEntityType::VoiceConversionModel => self.maybe_descriptive_text_voice_conversion_model_title,
UserBookmarkEntityType::ZsVoice => self.maybe_descriptive_text_zs_voice_title,
},
user_token: self.user_token,
username: self.username,
@@ -0,0 +1,7 @@
pub mod delete_user_bookmark;
pub mod create_user_bookmark;
pub mod user_bookmark_entity_token;
pub mod get_user_bookmark;
pub mod list_user_bookmarks_for_entity;
pub mod list_user_bookmarks;
pub mod list_user_bookmarks_result;
@@ -0,0 +1,48 @@
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
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 UserBookmarkEntityToken {
User(UserToken),
TtsModel(TtsModelToken),
TtsResult(TtsResultToken),
W2lTemplate(W2lTemplateToken),
W2lResult(W2lResultToken),
MediaFile(MediaFileToken),
VoiceConversionModel(VoiceConversionModelToken),
ZsVoice(ZsVoiceToken),
}
impl UserBookmarkEntityToken {
pub fn from_entity_type_and_token(entity_type: UserBookmarkEntityType, token: &str) -> Self {
match entity_type {
UserBookmarkEntityType::User => Self::User(UserToken::new_from_str(token)),
UserBookmarkEntityType::TtsModel => Self::TtsModel(TtsModelToken::new_from_str(token)),
UserBookmarkEntityType::TtsResult => Self::TtsResult(TtsResultToken::new_from_str(token)),
UserBookmarkEntityType::W2lTemplate => Self::W2lTemplate(W2lTemplateToken::new_from_str(token)),
UserBookmarkEntityType::W2lResult => Self::W2lResult(W2lResultToken::new_from_str(token)),
UserBookmarkEntityType::MediaFile => Self::MediaFile(MediaFileToken::new_from_str(token)),
UserBookmarkEntityType::VoiceConversionModel => Self::VoiceConversionModel(VoiceConversionModelToken::new_from_str(token)),
UserBookmarkEntityType::ZsVoice => Self::ZsVoice(ZsVoiceToken::new_from_str(token)),
}
}
pub fn get_composite_keys(&self) -> (UserBookmarkEntityType, &str) {
match self {
UserBookmarkEntityToken::User(user_token) => (UserBookmarkEntityType::User, user_token.as_str()),
UserBookmarkEntityToken::TtsModel(tts_model_token) => (UserBookmarkEntityType::TtsModel, tts_model_token.as_str()),
UserBookmarkEntityToken::TtsResult(tts_result_token) => (UserBookmarkEntityType::TtsResult, tts_result_token.as_str()),
UserBookmarkEntityToken::W2lTemplate(w2l_template_token) => (UserBookmarkEntityType::W2lTemplate, w2l_template_token.as_str()),
UserBookmarkEntityToken::W2lResult(w2l_result_token) => (UserBookmarkEntityType::W2lResult, w2l_result_token.as_str()),
UserBookmarkEntityToken::MediaFile(media_file_token) => (UserBookmarkEntityType::MediaFile, media_file_token.as_str()),
UserBookmarkEntityToken::VoiceConversionModel(voice_conversion_model_token) => (UserBookmarkEntityType::VoiceConversionModel, voice_conversion_model_token.as_str()),
UserBookmarkEntityToken::ZsVoice(zs_voice_token) => (UserBookmarkEntityType::ZsVoice, zs_voice_token.as_str()),
}
}
}
@@ -1,122 +0,0 @@
// 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))
}
@@ -1,5 +0,0 @@
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,5 +25,5 @@ pub mod voice_clone_requests;
pub mod voice_conversion;
pub mod voice_designer;
pub mod w2l;
pub mod favorites;
pub mod user_bookmarks;
pub mod weights;
@@ -11,46 +11,46 @@ 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 enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
use mysql_queries::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
use mysql_queries::queries::user_bookmarks::create_user_bookmark::{create_user_bookmark, CreateUserBookmarkArgs};
use tokens::tokens::user_bookmarks::UserBookmarkToken;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::server_state::ServerState;
#[derive(Deserialize)]
pub struct CreateFavoriteRequest {
pub struct CreateUserBookmarkRequest {
entity_token: String,
entity_type: FavoriteEntityType,
entity_type: UserBookmarkEntityType,
}
#[derive(Serialize)]
pub struct CreateFavoriteSuccessResponse {
pub struct CreateUserBookmarkSuccessResponse {
pub success: bool,
pub favorite_token: FavoriteToken,
pub user_bookmark_token: UserBookmarkToken,
}
#[derive(Debug)]
pub enum CreateFavoriteError {
pub enum CreateUserBookmarkError {
BadInput(String),
NotAuthorized,
ServerError,
}
impl ResponseError for CreateFavoriteError {
impl ResponseError for CreateUserBookmarkError {
fn status_code(&self) -> StatusCode {
match *self {
CreateFavoriteError::BadInput(_) => StatusCode::BAD_REQUEST,
CreateFavoriteError::NotAuthorized => StatusCode::UNAUTHORIZED,
CreateFavoriteError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
CreateUserBookmarkError::BadInput(_) => StatusCode::BAD_REQUEST,
CreateUserBookmarkError::NotAuthorized => StatusCode::UNAUTHORIZED,
CreateUserBookmarkError::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(),
CreateUserBookmarkError::BadInput(reason) => reason.to_string(),
CreateUserBookmarkError::NotAuthorized => "unauthorized".to_string(),
CreateUserBookmarkError::ServerError => "server error".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
@@ -58,24 +58,24 @@ impl ResponseError for CreateFavoriteError {
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for CreateFavoriteError {
impl fmt::Display for CreateUserBookmarkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn create_favorite_handler(
pub async fn create_user_bookmark_handler(
http_request: HttpRequest,
request: web::Json<CreateFavoriteRequest>,
request: web::Json<CreateUserBookmarkRequest>,
server_state: web::Data<Arc<ServerState>>,
) -> Result<HttpResponse, CreateFavoriteError>
) -> Result<HttpResponse, CreateUserBookmarkError>
{
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await
.map_err(|err| {
warn!("MySql pool error: {:?}", err);
CreateFavoriteError::ServerError
CreateUserBookmarkError::ServerError
})?;
let maybe_user_session = server_state
@@ -84,42 +84,42 @@ pub async fn create_favorite_handler(
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
CreateFavoriteError::ServerError
CreateUserBookmarkError::ServerError
})?;
let user_session = match maybe_user_session {
Some(session) => session,
None => {
warn!("not logged in");
return Err(CreateFavoriteError::NotAuthorized);
return Err(CreateUserBookmarkError::NotAuthorized);
}
};
let entity_token = FavoriteEntityToken::from_entity_type_and_token(
let entity_token = UserBookmarkEntityToken::from_entity_type_and_token(
request.entity_type, &request.entity_token);
let query_result = create_favorite(CreateFavoriteArgs {
let query_result = create_user_bookmark(CreateUserBookmarkArgs {
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 {
let user_bookmark_token = match query_result {
Ok(token) => token,
Err(err) => {
warn!("error inserting favorite: {:?}", err);
return Err(CreateFavoriteError::ServerError);
warn!("error inserting user_bookmark: {:?}", err);
return Err(CreateUserBookmarkError::ServerError);
}
};
let response = CreateFavoriteSuccessResponse {
let response = CreateUserBookmarkSuccessResponse {
success: true,
favorite_token,
user_bookmark_token,
};
let body = serde_json::to_string(&response)
.map_err(|_e| CreateFavoriteError::ServerError)?;
.map_err(|_e| CreateUserBookmarkError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
@@ -12,8 +12,8 @@ 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 mysql_queries::queries::user_bookmarks::delete_user_bookmark::delete_user_bookmark;
use tokens::tokens::user_bookmarks::UserBookmarkToken;
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;
@@ -21,40 +21,40 @@ use crate::server_state::ServerState;
/// For the URL PathInfo
#[derive(Deserialize)]
pub struct DeleteFavoritePathInfo {
favorite_token: FavoriteToken,
pub struct DeleteUserBookmarkPathInfo {
user_bookmark_token: UserBookmarkToken,
}
#[derive(Deserialize)]
pub struct DeleteFavoriteRequest {
pub struct DeleteUserBookmarkRequest {
/// 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 {
pub enum DeleteUserBookmarkError {
BadInput(String),
NotAuthorized,
NotFound,
ServerError,
}
impl ResponseError for DeleteFavoriteError {
impl ResponseError for DeleteUserBookmarkError {
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,
DeleteUserBookmarkError::BadInput(_) => StatusCode::BAD_REQUEST,
DeleteUserBookmarkError::NotAuthorized => StatusCode::UNAUTHORIZED,
DeleteUserBookmarkError::NotFound => StatusCode::NOT_FOUND,
DeleteUserBookmarkError::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(),
DeleteUserBookmarkError::BadInput(reason) => reason.to_string(),
DeleteUserBookmarkError::NotAuthorized => "unauthorized".to_string(),
DeleteUserBookmarkError::NotFound => "not found".to_string(),
DeleteUserBookmarkError::ServerError => "server error".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
@@ -62,24 +62,24 @@ impl ResponseError for DeleteFavoriteError {
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for DeleteFavoriteError {
impl fmt::Display for DeleteUserBookmarkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn delete_favorite_handler(
pub async fn delete_user_bookmark_handler(
http_request: HttpRequest,
path: Path<DeleteFavoritePathInfo>,
_request: web::Json<DeleteFavoriteRequest>,
path: Path<DeleteUserBookmarkPathInfo>,
_request: web::Json<DeleteUserBookmarkRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, DeleteFavoriteError> {
) -> Result<HttpResponse, DeleteUserBookmarkError> {
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await
.map_err(|err| {
warn!("MySql pool error: {:?}", err);
DeleteFavoriteError::ServerError
DeleteUserBookmarkError::ServerError
})?;
let maybe_user_session = server_state
@@ -88,18 +88,18 @@ pub async fn delete_favorite_handler(
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
DeleteFavoriteError::ServerError
DeleteUserBookmarkError::ServerError
})?;
let user_session = match maybe_user_session {
Some(session) => session,
None => {
return Err(DeleteFavoriteError::NotAuthorized);
return Err(DeleteUserBookmarkError::NotAuthorized);
}
};
let query_result = delete_favorite(
&path.favorite_token,
let query_result = delete_user_bookmark(
&path.user_bookmark_token,
&user_session.user_token_typed,
&mut *mysql_connection
).await;
@@ -108,7 +108,7 @@ pub async fn delete_favorite_handler(
Ok(_) => {},
Err(err) => {
warn!("Update tts mod approval status DB error: {:?}", err);
return Err(DeleteFavoriteError::ServerError);
return Err(DeleteUserBookmarkError::ServerError);
}
};
@@ -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::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
use mysql_queries::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
use mysql_queries::queries::user_bookmarks::list_user_bookmarks_for_entity::list_user_bookmarks_for_entity;
use tokens::tokens::user_bookmarks::UserBookmarkToken;
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 ListUserBookmarkPathInfo {
entity_type: UserBookmarkEntityType,
entity_token: String,
}
#[derive(Serialize)]
pub struct ListUserBookmarksSuccessResponse {
pub success: bool,
pub user_bookmarks: Vec<UserBookmark>,
}
#[derive(Serialize)]
pub struct UserBookmark {
pub token: UserBookmarkToken,
pub user: UserDetailsLight,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug)]
pub enum ListUserBookmarksError {
ServerError,
}
impl ResponseError for ListUserBookmarksError {
fn status_code(&self) -> StatusCode {
match *self {
ListUserBookmarksError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let error_reason = match self {
ListUserBookmarksError::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 ListUserBookmarksError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn list_user_bookmarks_for_entity_handler(
_http_request: HttpRequest,
path: Path<ListUserBookmarkPathInfo>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, ListUserBookmarksError>
{
let entity_token = UserBookmarkEntityToken::from_entity_type_and_token(
path.entity_type, &path.entity_token);
let query_results = list_user_bookmarks_for_entity(
entity_token,
&server_state.mysql_pool,
).await;
let user_bookmarks = match query_results {
Ok(results) => results,
Err(e) => {
warn!("Query error: {:?}", e);
return Err(ListUserBookmarksError::ServerError);
}
};
let response = ListUserBookmarksSuccessResponse {
success: true,
user_bookmarks: user_bookmarks.into_iter()
.map(|user_bookmark| UserBookmark {
token: user_bookmark.token,
user: UserDetailsLight {
user_token: user_bookmark.user_token.clone(),
username: user_bookmark.username.to_string(), // NB: Cloned because of ref use for avatar below
display_name: user_bookmark.user_display_name.clone(),
gravatar_hash: user_bookmark.user_gravatar_hash.clone(),
default_avatar: DefaultAvatarInfo::from_username(&user_bookmark.username),
},
created_at: user_bookmark.created_at,
updated_at: user_bookmark.updated_at,
})
.collect(),
};
let body = serde_json::to_string(&response)
.map_err(|_e| ListUserBookmarksError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
@@ -13,38 +13,38 @@ 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 enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
use mysql_queries::queries::user_bookmarks::list_user_bookmarks::{list_user_bookmarks, list_user_user_bookmarks_by_entity_type};
use tokens::tokens::user_bookmarks::UserBookmarkToken;
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>,
pub struct ListUserBookmarksQueryData {
maybe_scoped_entity_type: Option<UserBookmarkEntityType>,
}
#[derive(Serialize)]
pub struct ListFavoritesSuccessResponse {
pub struct ListUserBookmarksSuccessResponse {
pub success: bool,
pub favorites: Vec<Favorite>,
pub user_bookmarks: Vec<UserBookmark>,
}
#[derive(Serialize)]
pub struct Favorite {
pub token: FavoriteToken,
pub struct UserBookmark {
pub token: UserBookmarkToken,
pub details: FavoriteDetails,
pub details: UserBookmarkDetails,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Serialize)]
pub struct FavoriteDetails {
pub struct UserBookmarkDetails {
// TODO: This needs titles or some other summary metadata.
pub entity_type: FavoriteEntityType,
pub entity_type: UserBookmarkEntityType,
pub entity_token: String,
// TODO: Populate this for TTS
@@ -55,23 +55,23 @@ pub struct FavoriteDetails {
}
#[derive(Debug)]
pub enum ListFavoritesError {
pub enum ListUserBookmarksError {
ServerError,
NotAuthorizedError,
}
impl ResponseError for ListFavoritesError {
impl ResponseError for ListUserBookmarksError {
fn status_code(&self) -> StatusCode {
match *self {
ListFavoritesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
ListFavoritesError::NotAuthorizedError => StatusCode::UNAUTHORIZED,
ListUserBookmarksError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
ListUserBookmarksError::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(),
ListUserBookmarksError::ServerError => "server error".to_string(),
ListUserBookmarksError::NotAuthorizedError => "not authorized".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
@@ -79,61 +79,61 @@ impl ResponseError for ListFavoritesError {
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for ListFavoritesError {
impl fmt::Display for ListUserBookmarksError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn list_favorites_for_session_handler(
pub async fn list_user_bookmarks_for_session_handler(
http_request: HttpRequest,
query: Query<ListFavoritesQueryData>,
query: Query<ListUserBookmarksQueryData>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, ListFavoritesError>
) -> Result<HttpResponse, ListUserBookmarksError>
{
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
ListUserBookmarksError::ServerError
})?
.ok_or(ListFavoritesError::NotAuthorizedError)?;
.ok_or(ListUserBookmarksError::NotAuthorizedError)?;
let query_results = match query.maybe_scoped_entity_type {
None => list_user_favorites(&user_session.username, &server_state.mysql_pool).await,
None => list_user_bookmarks(&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)
list_user_user_bookmarks_by_entity_type(&user_session.username, entity_type, &server_state.mysql_pool)
.await,
};
let favorites = match query_results {
let user_bookmarks = match query_results {
Ok(results) => results,
Err(e) => {
warn!("Query error: {:?}", e);
return Err(ListFavoritesError::ServerError);
return Err(ListUserBookmarksError::ServerError);
}
};
let response = ListFavoritesSuccessResponse {
let response = ListUserBookmarksSuccessResponse {
success: true,
favorites: favorites.into_iter()
.map(|favorite| Favorite {
token: favorite.token,
details: FavoriteDetails {
entity_type: favorite.entity_type,
entity_token: favorite.entity_token,
user_bookmarks: user_bookmarks.into_iter()
.map(|user_bookmark| UserBookmark {
token: user_bookmark.token,
details: UserBookmarkDetails {
entity_type: user_bookmark.entity_type,
entity_token: user_bookmark.entity_token,
maybe_summary_text: None,
maybe_thumbnail_url: None,
},
created_at: favorite.created_at,
updated_at: favorite.updated_at,
created_at: user_bookmark.created_at,
updated_at: user_bookmark.updated_at,
})
.collect(),
};
let body = serde_json::to_string(&response)
.map_err(|_e| ListFavoritesError::ServerError)?;
.map_err(|_e| ListUserBookmarksError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
@@ -13,43 +13,43 @@ 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 enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
use mysql_queries::queries::user_bookmarks::list_user_bookmarks::{list_user_bookmarks, list_user_user_bookmarks_by_entity_type};
use tokens::tokens::user_bookmarks::UserBookmarkToken;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::server_state::ServerState;
#[derive(Deserialize)]
pub struct ListFavoritesPathInfo {
pub struct ListUserBookmarksPathInfo {
username: String,
}
#[derive(Deserialize)]
pub struct ListFavoritesQueryData {
maybe_scoped_entity_type: Option<FavoriteEntityType>,
pub struct ListUserBookmarksQueryData {
maybe_scoped_entity_type: Option<UserBookmarkEntityType>,
}
#[derive(Serialize)]
pub struct ListFavoritesSuccessResponse {
pub struct ListUserBookmarksSuccessResponse {
pub success: bool,
pub favorites: Vec<Favorite>,
pub user_bookmarks: Vec<UserBookmark>,
}
#[derive(Serialize)]
pub struct Favorite {
pub token: FavoriteToken,
pub struct UserBookmark {
pub token: UserBookmarkToken,
pub details: FavoriteDetails,
pub details: UserBookmarkDetails,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Serialize)]
pub struct FavoriteDetails {
pub struct UserBookmarkDetails {
// TODO: This needs titles or some other summary metadata.
pub entity_type: FavoriteEntityType,
pub entity_type: UserBookmarkEntityType,
pub entity_token: String,
// TODO: Populate this for TTS
@@ -60,20 +60,20 @@ pub struct FavoriteDetails {
}
#[derive(Debug)]
pub enum ListFavoritesError {
pub enum ListUserBookmarksError {
ServerError,
}
impl ResponseError for ListFavoritesError {
impl ResponseError for ListUserBookmarksError {
fn status_code(&self) -> StatusCode {
match *self {
ListFavoritesError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR,
ListUserBookmarksError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let error_reason = match self {
ListFavoritesError::ServerError => "server error".to_string(),
ListUserBookmarksError::ServerError => "server error".to_string(),
};
to_simple_json_error(&error_reason, self.status_code())
@@ -81,56 +81,56 @@ impl ResponseError for ListFavoritesError {
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl fmt::Display for ListFavoritesError {
impl fmt::Display for ListUserBookmarksError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
pub async fn list_favorites_for_user_handler(
pub async fn list_user_bookmarks_for_user_handler(
_http_request: HttpRequest,
path: Path<ListFavoritesPathInfo>,
query: Query<ListFavoritesQueryData>,
path: Path<ListUserBookmarksPathInfo>,
query: Query<ListUserBookmarksQueryData>,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, ListFavoritesError>
) -> Result<HttpResponse, ListUserBookmarksError>
{
let query_results = match query.maybe_scoped_entity_type {
None => list_user_favorites(&path.username, &server_state.mysql_pool).await,
None => list_user_bookmarks(&path.username, &server_state.mysql_pool).await,
Some(entity_type) =>
list_user_favorites_by_entity_type(&path.username, entity_type, &server_state.mysql_pool)
list_user_user_bookmarks_by_entity_type(&path.username, entity_type, &server_state.mysql_pool)
.await,
};
let favorites = match query_results {
let user_bookmarks = match query_results {
Ok(results) => results,
Err(e) => {
warn!("Query error: {:?}", e);
return Err(ListFavoritesError::ServerError);
return Err(ListUserBookmarksError::ServerError);
}
};
let response = ListFavoritesSuccessResponse {
let response = ListUserBookmarksSuccessResponse {
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,
user_bookmarks: user_bookmarks.into_iter()
.map(|user_bookmark| UserBookmark {
token: user_bookmark.token,
details: UserBookmarkDetails {
entity_type: user_bookmark.entity_type,
entity_token: user_bookmark.entity_token,
maybe_summary_text: user_bookmark.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,
created_at: user_bookmark.created_at,
updated_at: user_bookmark.updated_at,
})
.collect(),
};
let body = serde_json::to_string(&response)
.map_err(|_e| ListFavoritesError::ServerError)?;
.map_err(|_e| ListUserBookmarksError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
@@ -0,0 +1,5 @@
pub mod create_user_bookmark_handler;
pub mod delete_user_bookmark_handler;
pub mod list_user_bookmarks_for_entity_handler;
pub mod list_user_bookmarks_for_session_handler;
pub mod list_user_bookmarks_for_user_handler;
@@ -28,11 +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::user_bookmarks::create_user_bookmark_handler::create_user_bookmark_handler;
use crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::delete_user_bookmark_handler;
use crate::http_server::endpoints::user_bookmarks::list_user_bookmarks_for_entity_handler::list_user_bookmarks_for_entity_handler;
use crate::http_server::endpoints::user_bookmarks::list_user_bookmarks_for_session_handler::list_user_bookmarks_for_session_handler;
use crate::http_server::endpoints::user_bookmarks::list_user_bookmarks_for_user_handler::list_user_bookmarks_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;
@@ -202,14 +202,14 @@ 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 ====================
// ==================== User Bookmarks ====================
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)
.add_post("/v1/user_bookmarks/create", create_user_bookmark_handler)
.add_post("/v1/user_bookmarks/delete/{user_bookmark_token}", delete_user_bookmark_handler)
.add_get("/v1/user_bookmarks/list/session", list_user_bookmarks_for_session_handler)
.add_get("/v1/user_bookmarks/list/user/{username}", list_user_bookmarks_for_user_handler)
.add_get("/v1/user_bookmarks/list/entity/{entity_type}/{entity_token}", list_user_bookmarks_for_entity_handler)
.into_app();
// ==================== Animations ====================