diff --git a/_sql/migrations_squashed/user_tables/users.sql b/_sql/migrations_squashed/user_tables/users.sql index d99f9e4d73..f82eefefdd 100644 --- a/_sql/migrations_squashed/user_tables/users.sql +++ b/_sql/migrations_squashed/user_tables/users.sql @@ -30,6 +30,7 @@ CREATE TABLE users ( -- Possible flag values (multiple is okay) : -- - "explore_media" -- - "studio" + -- - "video_style_transfer" maybe_feature_flags VARCHAR(255) DEFAULT NULL, -- DEPRECATED diff --git a/crates/_types/enums/src/by_table/mod.rs b/crates/_types/enums/src/by_table/mod.rs index d4a62bd7af..96b5275c14 100644 --- a/crates/_types/enums/src/by_table/mod.rs +++ b/crates/_types/enums/src/by_table/mod.rs @@ -17,6 +17,7 @@ pub mod tts_models; pub mod usages; pub mod user_bookmarks; pub mod user_ratings; +pub mod users; pub mod voice_conversion_models; pub mod voice_conversion_results; pub mod zs_voices; diff --git a/crates/_types/enums/src/by_table/users/mod.rs b/crates/_types/enums/src/by_table/users/mod.rs new file mode 100644 index 0000000000..0027d1743a --- /dev/null +++ b/crates/_types/enums/src/by_table/users/mod.rs @@ -0,0 +1 @@ +pub mod user_feature_flag; \ No newline at end of file diff --git a/crates/_types/enums/src/by_table/users/user_feature_flag.rs b/crates/_types/enums/src/by_table/users/user_feature_flag.rs new file mode 100644 index 0000000000..5520b303b4 --- /dev/null +++ b/crates/_types/enums/src/by_table/users/user_feature_flag.rs @@ -0,0 +1,133 @@ +use std::collections::BTreeSet; + +use serde::Deserialize; +use serde::Serialize; +#[cfg(test)] +use strum::EnumCount; +#[cfg(test)] +use strum::EnumIter; +use utoipa::ToSchema; + +/// Used in the `users` table in a `VARCHAR` field (stored as comma separated set). +/// +/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY. +#[cfg_attr(test, derive(EnumIter, EnumCount))] +#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum UserFeatureFlag { + /// Grants a user the ability to list media + /// (For now, it's hidden until we get an NSFW filter.) + ExploreMedia, + + /// Access to studio features + Studio, + + /// Access to video style transfer + VideoStyleTransfer, +} + +// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests! +impl_enum_display_and_debug_using_to_str!(UserFeatureFlag); +impl_mysql_enum_coders!(UserFeatureFlag); +impl_mysql_from_row!(UserFeatureFlag); + +/// NB: Legacy API for older code. +impl UserFeatureFlag { + pub fn to_str(&self) -> &'static str { + match self { + Self::ExploreMedia => "explore_media", + Self::Studio => "studio", + Self::VideoStyleTransfer => "video_style_transfer", + } + } + + pub fn from_str(value: &str) -> Result { + match value { + "explore_media" => Ok(Self::ExploreMedia), + "studio" => Ok(Self::Studio), + "video_style_transfer" => Ok(Self::VideoStyleTransfer), + _ => Err(format!("invalid value: {:?}", value)), + } + } + + pub fn all_variants() -> BTreeSet { + // NB: BTreeSet is sorted + // NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc. + BTreeSet::from([ + Self::ExploreMedia, + Self::Studio, + Self::VideoStyleTransfer, + ]) + } +} + +#[cfg(test)] +mod tests { + use crate::test_helpers::assert_serialization; + use crate::by_table::users::user_feature_flag::UserFeatureFlag; + + mod explicit_checks { + use super::*; + + #[test] + fn test_serialization() { + assert_serialization(UserFeatureFlag::ExploreMedia, "explore_media"); + assert_serialization(UserFeatureFlag::Studio, "studio"); + assert_serialization(UserFeatureFlag::VideoStyleTransfer, "video_style_transfer"); + } + + #[test] + fn test_to_str() { + assert_eq!(UserFeatureFlag::ExploreMedia.to_str(), "explore_media"); + assert_eq!(UserFeatureFlag::Studio.to_str(), "studio"); + assert_eq!(UserFeatureFlag::VideoStyleTransfer.to_str(), "video_style_transfer"); + } + + #[test] + fn test_from_str() { + assert_eq!(UserFeatureFlag::from_str("explore_media").unwrap(), UserFeatureFlag::ExploreMedia); + assert_eq!(UserFeatureFlag::from_str("studio").unwrap(), UserFeatureFlag::Studio); + assert_eq!(UserFeatureFlag::from_str("video_style_transfer").unwrap(), UserFeatureFlag::VideoStyleTransfer); + assert!(UserFeatureFlag::from_str("foo").is_err()); + } + + #[test] + fn all_variants() { + let mut variants = UserFeatureFlag::all_variants(); + assert_eq!(variants.len(), 3); + assert_eq!(variants.pop_first(), Some(UserFeatureFlag::ExploreMedia)); + assert_eq!(variants.pop_first(), Some(UserFeatureFlag::Studio)); + assert_eq!(variants.pop_first(), Some(UserFeatureFlag::VideoStyleTransfer)); + assert_eq!(variants.pop_first(), None); + } + } + + mod mechanical_checks { + use super::*; + + #[test] + fn variant_length() { + use strum::IntoEnumIterator; + assert_eq!(UserFeatureFlag::all_variants().len(), UserFeatureFlag::iter().len()); + } + + #[test] + fn round_trip() { + for variant in UserFeatureFlag::all_variants() { + assert_eq!(variant, UserFeatureFlag::from_str(variant.to_str()).unwrap()); + assert_eq!(variant, UserFeatureFlag::from_str(&format!("{}", variant)).unwrap()); + assert_eq!(variant, UserFeatureFlag::from_str(&format!("{:?}", variant)).unwrap()); + } + } + + #[test] + fn serialized_length_ok_for_database() { + const MAX_LENGTH : usize = 32; + for variant in UserFeatureFlag::all_variants() { + let serialized = variant.to_str(); + assert!(serialized.len() > 0, "variant {:?} is too short", variant); + assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant); + } + } + } +}