Merge branch 'master' into feature/comfyui-sidecar

This commit is contained in:
saltacc authored and GitHub committed 2024-03-07 14:03:03 -08:00
commit efe26f9be4
28 files changed
+660 -201

No files matched your search

@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n users.token as user_token,\n users.username,\n users.display_name,\n\n users.email_address,\n users.email_confirmed,\n users.email_gravatar_hash,\n\n users.maybe_stripe_customer_id,\n users.maybe_loyalty_program_key,\n\n users.disable_gravatar,\n users.auto_play_audio_preference,\n users.auto_play_video_preference,\n users.preferred_tts_result_visibility as `preferred_tts_result_visibility: enums::common::visibility::Visibility`,\n users.preferred_w2l_result_visibility as `preferred_w2l_result_visibility: enums::common::visibility::Visibility`,\n\n users.user_role_slug,\n users.is_banned,\n\n users.can_access_studio,\n\n user_roles.can_use_tts,\n user_roles.can_use_w2l,\n user_roles.can_delete_own_tts_results,\n user_roles.can_delete_own_w2l_results,\n user_roles.can_delete_own_account,\n\n user_roles.can_upload_tts_models,\n user_roles.can_upload_w2l_templates,\n user_roles.can_delete_own_tts_models,\n user_roles.can_delete_own_w2l_templates,\n\n user_roles.can_approve_w2l_templates,\n user_roles.can_edit_other_users_profiles,\n user_roles.can_edit_other_users_tts_models,\n user_roles.can_edit_other_users_w2l_templates,\n user_roles.can_delete_other_users_tts_models,\n user_roles.can_delete_other_users_tts_results,\n user_roles.can_delete_other_users_w2l_templates,\n user_roles.can_delete_other_users_w2l_results,\n user_roles.can_ban_users,\n user_roles.can_delete_users\n\nFROM users\nLEFT OUTER JOIN user_sessions\n ON users.token = user_sessions.user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE user_sessions.token = ?\n AND user_sessions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\n ",
"query": "\nSELECT\n users.token as user_token,\n users.username,\n users.display_name,\n\n users.email_address,\n users.email_confirmed,\n users.email_gravatar_hash,\n\n users.maybe_stripe_customer_id,\n users.maybe_loyalty_program_key,\n\n users.disable_gravatar,\n users.auto_play_audio_preference,\n users.auto_play_video_preference,\n users.preferred_tts_result_visibility as `preferred_tts_result_visibility: enums::common::visibility::Visibility`,\n users.preferred_w2l_result_visibility as `preferred_w2l_result_visibility: enums::common::visibility::Visibility`,\n\n users.user_role_slug,\n users.is_banned,\n\n users.can_access_studio,\n users.maybe_feature_flags,\n\n user_roles.can_use_tts,\n user_roles.can_use_w2l,\n user_roles.can_delete_own_tts_results,\n user_roles.can_delete_own_w2l_results,\n user_roles.can_delete_own_account,\n\n user_roles.can_upload_tts_models,\n user_roles.can_upload_w2l_templates,\n user_roles.can_delete_own_tts_models,\n user_roles.can_delete_own_w2l_templates,\n\n user_roles.can_approve_w2l_templates,\n user_roles.can_edit_other_users_profiles,\n user_roles.can_edit_other_users_tts_models,\n user_roles.can_edit_other_users_w2l_templates,\n user_roles.can_delete_other_users_tts_models,\n user_roles.can_delete_other_users_tts_results,\n user_roles.can_delete_other_users_w2l_templates,\n user_roles.can_delete_other_users_w2l_results,\n user_roles.can_ban_users,\n user_roles.can_delete_users\n\nFROM users\nLEFT OUTER JOIN user_sessions\n ON users.token = user_sessions.user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE user_sessions.token = ?\n AND user_sessions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\n ",
"describe": {
"columns": [
{
@@ -165,6 +165,16 @@
},
{
"ordinal": 16,
"name": "maybe_feature_flags",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 17,
"name": "can_use_tts",
"type_info": {
"type": "Tiny",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "can_use_w2l",
"type_info": {
"type": "Tiny",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "can_delete_own_tts_results",
"type_info": {
"type": "Tiny",
@@ -194,7 +204,7 @@
}
},
{
"ordinal": 19,
"ordinal": 20,
"name": "can_delete_own_w2l_results",
"type_info": {
"type": "Tiny",
@@ -204,7 +214,7 @@
}
},
{
"ordinal": 20,
"ordinal": 21,
"name": "can_delete_own_account",
"type_info": {
"type": "Tiny",
@@ -214,7 +224,7 @@
}
},
{
"ordinal": 21,
"ordinal": 22,
"name": "can_upload_tts_models",
"type_info": {
"type": "Tiny",
@@ -224,7 +234,7 @@
}
},
{
"ordinal": 22,
"ordinal": 23,
"name": "can_upload_w2l_templates",
"type_info": {
"type": "Tiny",
@@ -234,7 +244,7 @@
}
},
{
"ordinal": 23,
"ordinal": 24,
"name": "can_delete_own_tts_models",
"type_info": {
"type": "Tiny",
@@ -244,7 +254,7 @@
}
},
{
"ordinal": 24,
"ordinal": 25,
"name": "can_delete_own_w2l_templates",
"type_info": {
"type": "Tiny",
@@ -254,7 +264,7 @@
}
},
{
"ordinal": 25,
"ordinal": 26,
"name": "can_approve_w2l_templates",
"type_info": {
"type": "Tiny",
@@ -264,7 +274,7 @@
}
},
{
"ordinal": 26,
"ordinal": 27,
"name": "can_edit_other_users_profiles",
"type_info": {
"type": "Tiny",
@@ -274,7 +284,7 @@
}
},
{
"ordinal": 27,
"ordinal": 28,
"name": "can_edit_other_users_tts_models",
"type_info": {
"type": "Tiny",
@@ -284,7 +294,7 @@
}
},
{
"ordinal": 28,
"ordinal": 29,
"name": "can_edit_other_users_w2l_templates",
"type_info": {
"type": "Tiny",
@@ -294,7 +304,7 @@
}
},
{
"ordinal": 29,
"ordinal": 30,
"name": "can_delete_other_users_tts_models",
"type_info": {
"type": "Tiny",
@@ -304,7 +314,7 @@
}
},
{
"ordinal": 30,
"ordinal": 31,
"name": "can_delete_other_users_tts_results",
"type_info": {
"type": "Tiny",
@@ -314,7 +324,7 @@
}
},
{
"ordinal": 31,
"ordinal": 32,
"name": "can_delete_other_users_w2l_templates",
"type_info": {
"type": "Tiny",
@@ -324,7 +334,7 @@
}
},
{
"ordinal": 32,
"ordinal": 33,
"name": "can_delete_other_users_w2l_results",
"type_info": {
"type": "Tiny",
@@ -334,7 +344,7 @@
}
},
{
"ordinal": 33,
"ordinal": 34,
"name": "can_ban_users",
"type_info": {
"type": "Tiny",
@@ -344,7 +354,7 @@
}
},
{
"ordinal": 34,
"ordinal": 35,
"name": "can_delete_users",
"type_info": {
"type": "Tiny",
@@ -392,8 +402,9 @@
true,
true,
true,
true,
true
]
},
"hash": "423109f2dcee23a558c43ecaab0eb2836714d5316620daaa1127b447cad59212"
"hash": "1c32bdb79ed2bc1b8988bbb6ea3f2a1db4ed4552d53cb4f31ff4b08de01691f7"
}
Generated
+1
View File
@@ -3241,6 +3241,7 @@ dependencies = [
"tokens",
"tokio",
"tts_common",
"videos",
"walkdir",
]
@@ -1,36 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Staff
-- teddanson is victor's alt
-- endtimes is @sugarbro (testing)
-- el_cid_93 is for testing
-- SELECT token, username FROM users WHERE username IN (
UPDATE users SET can_access_studio = true WHERE username IN (
'bflat',
'brandon',
'crossproduct',
'crossproduct1',
'echelon',
'el_cid_93',
'endtimes',
'fyscott',
'gateway',
'jags111',
'mrvintage',
'olivicmic',
'rewin123',
'saltacc',
'teddanson',
'vegito1089',
'wilwong',
'yae_ph',
'zzz_last_item'
);
UPDATE users SET can_access_studio = true WHERE username IN (
'endtimes',
'tropicalfun',
'zzz_last_item'
);
+62
View File
@@ -0,0 +1,62 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Staff (1)
-- * teddanson is victor's alt
UPDATE users
SET
can_access_studio = false,
maybe_feature_flags = 'explore_media,studio,video_style_transfer'
WHERE username IN (
'bflat',
'brandon',
'crossproduct',
'crossproduct1',
'echelon',
'olivicmic',
'saltacc',
'teddanson',
'vegito1089',
'wilwong',
'zzz_last_item'
);
-- Staff (2)
-- * el_cid_93 is for testing (who is this??)
-- * endtimes is @sugarbro (testing)
-- * tammieteller is Tammie (Pebblebed)
UPDATE users
SET
can_access_studio = false,
maybe_feature_flags = 'studio,video_style_transfer'
WHERE username IN (
'el_cid_93',
'endtimes',
'fyscott',
'gateway',
'jags111',
'justinjohn0306',
'mrvintage',
'rewin123',
'tammieteller',
'tropicalfun',
'yae_ph',
'zzz_last_item'
);
-- Early access to VST only
UPDATE users
SET
can_access_studio = false,
maybe_feature_flags = 'video_style_transfer'
WHERE username IN (
'kenjoplays',
'ofccccccc',
'sonicgt2',
'stewiegroffin',
'tanooki426',
'wawoul',
'zzz_last_item'
);
+24 -3
View File
@@ -6,30 +6,47 @@ use mp4::TrackType;
use errors::AnyhowResult;
#[derive(Debug, Clone)]
pub struct Mp4Info {
/// Framerate of the longest video track.
pub framerate: f64,
/// Duration of the entire mp4.
pub duration_millis: u128,
/// Width of the longest video track.
pub width: u16,
/// Height of the longest video track.
pub height: u16,
}
pub fn get_mp4_info_for_bytes(file_bytes: &[u8], file_size: u64) -> AnyhowResult<Mp4Info> {
pub fn get_mp4_info_for_bytes(file_bytes: &[u8]) -> AnyhowResult<Mp4Info> {
get_mp4_info_for_bytes_and_len(file_bytes, file_bytes.len())
}
pub fn get_mp4_info_for_bytes_and_len(file_bytes: &[u8], file_size: usize) -> AnyhowResult<Mp4Info> {
let reader = BufReader::new(Cursor::new(file_bytes));
get_mp4_info(reader, file_size)
get_mp4_info(reader, file_size as u64)
}
pub fn get_mp4_info<T: Seek + Read>(reader: T, file_size: u64) -> AnyhowResult<Mp4Info> {
let mp4 = mp4::Mp4Reader::read_header(reader, file_size)?;
let mut framerate = 0.0;
let mut longest_duration = Duration::from_secs(0);
let mut framerate = 0.0;
let mut width = 0;
let mut height = 0;
for track in mp4.tracks().values() {
match track.track_type() {
Ok(TrackType::Video) => {
if track.duration() > longest_duration {
longest_duration = track.duration();
framerate = track.frame_rate();
width = track.width();
height = track.height();
}
}
Err(err) => {
@@ -43,6 +60,8 @@ pub fn get_mp4_info<T: Seek + Read>(reader: T, file_size: u64) -> AnyhowResult<M
Ok(Mp4Info {
framerate,
duration_millis: mp4.duration().as_millis(),
width,
height,
})
}
@@ -76,5 +95,7 @@ pub mod tests {
assert_eq!(info.framerate, 30.0);
assert_eq!(info.duration_millis, 15168);
assert_eq!(info.width, 640);
assert_eq!(info.height, 480);
}
}
@@ -39,6 +39,9 @@ pub struct SessionUserRecord {
// ===== FEATURE FLAGS ===== //
// Optional comma-separated list of parseable `UserFeatureFlag` enum features
pub maybe_feature_flags: Option<String>,
pub can_access_studio: bool,
// ===== ROLE ===== //
@@ -121,6 +124,7 @@ SELECT
users.is_banned,
users.can_access_studio,
users.maybe_feature_flags,
user_roles.can_use_tts,
user_roles.can_use_w2l,
@@ -183,6 +187,7 @@ WHERE user_sessions.token = ?
is_banned: i8_to_bool(raw_user_record.is_banned),
can_access_studio: i8_to_bool(raw_user_record.can_access_studio),
maybe_feature_flags: raw_user_record.maybe_feature_flags,
// Usage
can_use_tts: nullable_i8_to_bool_default_false(raw_user_record.can_use_tts),
@@ -250,6 +255,8 @@ struct SessionUserRawDbRecord {
// Feature / Rollout Flags
can_access_studio: i8,
maybe_feature_flags: Option<String>,
// NB: These are `Option` due to the JOIN not being compile-time assured.
// Usage
can_use_tts: Option<i8>,
+3 -2
View File
@@ -19,8 +19,6 @@ actix_helpers = { path = "../../../lib/actix_helpers" }
bootstrap = { path = "../../../lib/bootstrap" }
buckets = { path = "../../../_types/buckets" }
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
google_drive_common = { path = "../../../lib/api_clients/google_drive_common" }
thumbnail_generator = { path = "../../../lib/api_clients/thumbnail_generator"}
collections = { path = "../../../lib/collections" }
composite_identifiers = { path = "../../../_types/composite_identifiers" }
concurrency = { path = "../../../lib/concurrency" }
@@ -29,6 +27,7 @@ easyenv = { path = "../../../lib/easyenv" }
enums = { path = "../../../_types/enums" }
errors = { path = "../../../lib/errors" }
filesys = { path = "../../../lib/files/filesys" }
google_drive_common = { path = "../../../lib/api_clients/google_drive_common" }
hashing = { path = "../../../lib/files/hashing" }
images = { path = "../../../lib/files/images" }
jobs_common = { path = "../../../lib/jobs_common" }
@@ -39,8 +38,10 @@ mimetypes = { path = "../../../lib/files/mimetypes" }
mysql_queries = { path = "../../../lib/mysql_queries" }
redis_common = { path = "../../../lib/redis_common" }
subprocess_common = { path = "../../../lib/subprocess_common" }
thumbnail_generator = { path = "../../../lib/api_clients/thumbnail_generator"}
tokens = { path = "../../../_types/tokens" }
tts_common = { path = "../../../lib/tts_common" }
videos = { path = "../../../lib/files/videos" }
# External
anyhow = "1.0.75"
@@ -1,4 +1,6 @@
use std::fs::read_to_string;
use std::fs::{File, read_to_string};
use std::io::{BufReader, Read};
use std::path::PathBuf;
use std::time::Instant;
use anyhow::anyhow;
@@ -14,6 +16,7 @@ use hashing::sha256::sha256_hash_file::sha256_hash_file;
use mysql_queries::payloads::generic_inference_args::generic_inference_args::PolymorphicInferenceArgs::Mc;
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use mysql_queries::queries::media_files::create::insert_media_file_from_mocapnet::{insert_media_file_from_mocapnet, InsertArgs};
use videos::get_mp4_info::{get_mp4_info, get_mp4_info_for_bytes, Mp4Info};
use crate::job::job_loop::job_success_result::{JobSuccessResult, ResultEntity};
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
@@ -53,6 +56,23 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
let job_args = validate_job(job)?;
// ==================== JOB ARGS ==================== //
let maybe_args = job.maybe_inference_args
.as_ref()
.map(|args| args.args.as_ref())
.flatten();
let poly_args = match maybe_args {
None => return Err(ProcessSingleJobError::Other(anyhow!("Mocap args not found"))),
Some(args) => args,
};
let mc_args = match poly_args {
Mc(args) => args,
_ => return Err(ProcessSingleJobError::Other(anyhow!("Mocap args not found"))),
};
// ==================== TEMP DIR ==================== //
let work_temp_dir = format!("temp_mocapnet_inference_{}", job.id.0);
@@ -86,6 +106,22 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
info!("Used video file: {:?}", usable_video_path);
// ==================== EXTRACT FILE METADATA (IF POSSIBLE) ==================== //
// TODO(bt): better check for video file type
let video_read_result = try_get_video_info(&video_path.filesystem_path);
let mut maybe_width = mc_args.maybe_size1.clone();
let mut maybe_height = mc_args.maybe_size2.clone();
// NB: Fail open. In the event this fails, we want to continue processing.
if let Some(video_info) = video_read_result {
info!("Read video info from file: {:?}", video_info);
maybe_width = Some(video_info.width as i32);
maybe_height = Some(video_info.height as i32);
}
// ==================== SETUP FOR INFERENCE ==================== //
info!("Ready for MocapNET inference...");
@@ -102,21 +138,6 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
// TODO: Limit output length for non-premium (???)
let maybe_args = job.maybe_inference_args
.as_ref()
.map(|args| args.args.as_ref())
.flatten();
let poly_args = match maybe_args {
None => return Err(ProcessSingleJobError::Other(anyhow!("Mocap args not found"))),
Some(args) => args,
};
let mc_args = match poly_args {
Mc(args) => args,
_ => return Err(ProcessSingleJobError::Other(anyhow!("Mocap args not found"))),
};
// ==================== RUN INFERENCE SCRIPT ==================== //
let stderr_output_file = work_temp_dir.path().join("stderr.txt");
let inference_start_time = Instant::now();
@@ -130,8 +151,8 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
maybe_ik3: &mc_args.maybe_ik3,
maybe_smoothing1: &mc_args.maybe_smoothing1,
maybe_smoothing2: &mc_args.maybe_smoothing2,
maybe_size1: &mc_args.maybe_size1,
maybe_size2: &mc_args.maybe_size2,
maybe_size1: &maybe_width,
maybe_size2: &maybe_height,
stderr_output_file: &stderr_output_file,
});
@@ -255,3 +276,12 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
inference_duration,
})
}
// Fail open if this doesn't work
fn try_get_video_info(video_filename: &PathBuf) -> Option<Mp4Info> {
let video_file = File::open(video_filename).ok()?;
let file_length = video_file.metadata().ok()?.len();
let reader = BufReader::new(video_file);
let info = get_mp4_info(reader, file_length).ok()?;
Some(info)
}
@@ -3,6 +3,7 @@
#![forbid(unused_mut)]
#![forbid(unused_variables)]
use std::collections::HashSet;
use std::fmt;
use actix_web::{HttpRequest, HttpResponse, web};
@@ -12,11 +13,13 @@ use log::warn;
use sqlx::MySqlPool;
use utoipa::ToSchema;
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
use http_server_common::response::response_error_helpers::to_simple_json_error;
use crate::common_responses::user_details_lite::UserDetailsLight;
use crate::cookies::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
use crate::utils::session_checker::SessionChecker;
use crate::utils::user_session_feature_flags::UserSessionFeatureFlags;
#[derive(Serialize, Copy, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
@@ -47,6 +50,7 @@ pub struct SessionUserInfo {
// Feature / rollout flags:
pub can_access_studio: bool,
pub maybe_feature_flags: HashSet<UserFeatureFlag>,
// Premium plans:
pub fakeyou_plan: FakeYouPlan,
@@ -149,6 +153,9 @@ pub async fn session_info_handler(
match maybe_user_session {
None => {}
Some(session_data) => {
let feature_flags =
UserSessionFeatureFlags::new(session_data.maybe_feature_flags.as_deref());
if !session_data.is_banned {
// NB: Banned users can't be logged in
logged_in = true;
@@ -166,6 +173,7 @@ pub async fn session_info_handler(
// Rollout / feature flags:
can_access_studio: session_data.can_access_studio,
maybe_feature_flags: feature_flags.get_flags(),
// Premium plans:
fakeyou_plan: FakeYouPlan::Free,
@@ -1,3 +1,4 @@
pub mod email_to_gravatar;
pub mod session_checker;
pub mod user_session_extended;
pub mod user_session_extended;
pub mod user_session_feature_flags;
@@ -17,6 +17,7 @@ use redis_common::redis_cache_keys::RedisCacheKeys;
use crate::cookies::session::session_cookie_manager::SessionCookieManager;
use crate::utils::user_session_extended::{UserSessionExtended, UserSessionPreferences, UserSessionPremiumPlanInfo, UserSessionRoleAndPermissions, UserSessionSubscriptionPlan, UserSessionUserDetails};
use crate::utils::user_session_feature_flags::UserSessionFeatureFlags;
#[derive(Clone)]
pub struct SessionChecker {
@@ -168,7 +169,6 @@ impl SessionChecker {
}
// ==================== UserSessionExtended ====================
//#[deprecated = "Use the PoolConnection<MySql> method instead of the MySqlPool one."]
@@ -258,6 +258,9 @@ impl SessionChecker {
can_ban_users: user_session.can_ban_users,
can_delete_users: user_session.can_delete_users,
},
feature_flags: UserSessionFeatureFlags {
maybe_feature_flags: user_session.maybe_feature_flags,
}
}))
}
@@ -2,6 +2,8 @@ use chrono::{DateTime, Utc};
use enums::common::visibility::Visibility;
use crate::utils::user_session_feature_flags::UserSessionFeatureFlags;
#[derive(Clone, Default)]
pub struct UserSessionExtended {
pub user_token: String,
@@ -9,6 +11,7 @@ pub struct UserSessionExtended {
pub premium: UserSessionPremiumPlanInfo,
pub preferences: UserSessionPreferences,
pub role: UserSessionRoleAndPermissions,
pub feature_flags: UserSessionFeatureFlags,
}
#[derive(Clone, Default)]
@@ -96,3 +99,4 @@ pub struct UserSessionRoleAndPermissions {
pub can_ban_users: bool,
pub can_delete_users: bool,
}
@@ -0,0 +1,117 @@
use std::collections::HashSet;
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
#[derive(Default, Clone)]
pub struct UserSessionFeatureFlags {
// Optional comma-separated list of parseable `UserFeatureFlag` enum features
pub maybe_feature_flags: Option<String>,
}
impl UserSessionFeatureFlags {
pub fn new(maybe_feature_flags: Option<&str>) -> Self {
Self {
maybe_feature_flags: maybe_feature_flags.map(|s| s.to_string()),
}
}
pub fn get_flags(&self) -> HashSet<UserFeatureFlag> {
match self.maybe_feature_flags.as_deref() {
None => HashSet::new(),
Some(feature_flags) => {
feature_flags
.split(",")
.map(|flag| flag.trim())
.filter_map(|flag| UserFeatureFlag::from_str(flag).ok())
.collect()
}
}
}
// TODO(bt,2024-03-05): Caching
pub fn has_permission_unoptimized(&self, permission: UserFeatureFlag) -> bool {
self.get_flags().contains(&permission)
}
}
#[cfg(test)]
mod tests {
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
use crate::utils::user_session_feature_flags::UserSessionFeatureFlags;
#[test]
fn test_no_flags() {
let flags = UserSessionFeatureFlags::default();
assert_eq!(flags.get_flags().len(), 0);
for flag in UserFeatureFlag::all_variants() {
assert_eq!(flags.has_permission_unoptimized(flag), false);
}
// TODO(bt,2024-03-05): Expose strum to callers in test packages.
//for flag in UserFeatureFlag::iter() {
// assert_eq!(flags.has_permission_unoptimized(flag), false);
//}
}
#[test]
fn test_single_feature() {
let flags = UserSessionFeatureFlags {
maybe_feature_flags: Some("studio".to_string()),
};
assert_eq!(flags.get_flags().len(), 1);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), true);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), false);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), false);
}
#[test]
fn test_all_features() {
let flags = UserSessionFeatureFlags {
maybe_feature_flags: Some("studio,video_style_transfer,explore_media".to_string()),
};
assert_eq!(flags.get_flags().len(), 3);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), true);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), true);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), true);
}
#[test]
fn test_duplication() {
let flags = UserSessionFeatureFlags {
maybe_feature_flags: Some("studio,studio,studio,studio".to_string()),
};
assert_eq!(flags.get_flags().len(), 1);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), true);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), false);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), false);
}
#[test]
fn test_spacing() {
let flags = UserSessionFeatureFlags {
maybe_feature_flags: Some(" studio, video_style_transfer , , , ".to_string()),
};
assert_eq!(flags.get_flags().len(), 2);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), true);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), true);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), false);
}
#[test]
fn test_invalid_features_and_typos() {
let flags = UserSessionFeatureFlags {
maybe_feature_flags: Some("invalid,,typo,stdo,STUDIO".to_string()),
};
assert_eq!(flags.get_flags().len(), 0);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), false);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), false);
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), false);
}
}
@@ -44,6 +44,7 @@ use crate::http_server::endpoints::media_files::upload::upload_media_file_handle
use crate::http_server::endpoints::media_files::upload_video::upload_error::VideoMediaFileUploadError;
use crate::http_server::endpoints::media_files::upload_video::upload_video_media_file_handler::*;
use crate::http_server::endpoints::prompts::get_prompt::*;
use crate::http_server::endpoints::service::status_alert_handler::*;
use crate::http_server::endpoints::user_bookmarks::batch_get_user_bookmarks_handler::*;
use crate::http_server::endpoints::user_bookmarks::create_user_bookmark_handler::*;
use crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::*;
@@ -79,6 +80,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
crate::http_server::endpoints::media_files::upload::upload_media_file_handler::upload_media_file_handler,
crate::http_server::endpoints::media_files::upload_video::upload_video_media_file_handler::upload_video_media_file_handler,
crate::http_server::endpoints::prompts::get_prompt::get_prompt_handler,
crate::http_server::endpoints::service::status_alert_handler::status_alert_handler,
crate::http_server::endpoints::user_bookmarks::batch_get_user_bookmarks_handler::batch_get_user_bookmarks_handler,
crate::http_server::endpoints::user_bookmarks::create_user_bookmark_handler::create_user_bookmark_handler,
crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::delete_user_bookmark_handler,
@@ -233,10 +235,12 @@ use crate::http_server::web_utils::response_success_helpers::*;
SetUserRatingError,
SetUserRatingRequest,
SetUserRatingResponse,
StatusAlertCategory,
StatusAlertError,
StatusAlertInfo,
StatusAlertResponse,
StatusDetailsResponse,
StorytellerStreamPlan,
VideoMediaFileUploadError,
UploadVideoMediaSuccessResponse,
TerminateInferenceJobError,
TerminateInferenceJobPathInfo,
TerminateInferenceJobSuccessResponse,
@@ -244,6 +248,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
UpdateWeightPathInfo,
UpdateWeightRequest,
UploadMediaSuccessResponse,
UploadVideoMediaSuccessResponse,
UserBookmarkDetailsForUserList,
UserBookmarkEntityType,
UserBookmarkForEntityListItem,
@@ -253,6 +258,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
UserProfileUserBadge,
UserRatingEntityType,
UserRatingValue,
VideoMediaFileUploadError,
Weight,
WeightsData,
ZsDatasetRecord,
@@ -24,6 +24,7 @@ use crate::http_server::common_responses::pagination_cursors::PaginationCursors;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::server_state::ServerState;
use crate::util::allowed_explore_media_access::allowed_explore_media_access;
use crate::util::allowed_studio_access::allowed_studio_access;
#[derive(Deserialize, ToSchema, IntoParams)]
@@ -136,8 +137,8 @@ pub async fn list_media_files_handler(
// ==================== FEATURE FLAG CHECK ==================== //
if !allowed_studio_access(maybe_user_session.as_ref(), &server_state.flags) {
warn!("Storyteller Studio access is not permitted for user");
if !allowed_explore_media_access(maybe_user_session.as_ref()) {
warn!("Explore media access is not permitted for user");
return Err(ListMediaFilesError::NotAuthorized);
}
@@ -18,7 +18,7 @@ use mimetypes::mimetype_to_extension::mimetype_to_extension;
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::create::insert_media_file_from_file_upload::{insert_media_file_from_file_upload, InsertMediaFileFromUploadArgs, UploadType};
use tokens::tokens::media_files::MediaFileToken;
use videos::get_mp4_info::{get_mp4_info, get_mp4_info_for_bytes};
use videos::get_mp4_info::{get_mp4_info, get_mp4_info_for_bytes, get_mp4_info_for_bytes_and_len};
use crate::http_server::endpoints::engine::create_scene_handler::{CreateSceneError, CreateSceneSuccessResponse};
use crate::http_server::endpoints::media_files::upload_video::drain_multipart_request::drain_multipart_request;
@@ -176,7 +176,7 @@ pub async fn upload_video_media_file_handler(
// ==================== FRAME RATE ==================== //
let mp4_info = get_mp4_info_for_bytes(file_bytes.as_ref(), file_size_bytes as u64)
let mp4_info = get_mp4_info_for_bytes(file_bytes.as_ref())
.map_err(|err| {
warn!("Error reading mp4 info: {:?}", err);
VideoMediaFileUploadError::ServerError
@@ -1,2 +1,3 @@
pub mod health_check_handler;
pub mod public_info_handler;
pub mod public_info_handler;
pub mod status_alert_handler;
@@ -0,0 +1,127 @@
use std::sync::Arc;
use actix_web::{HttpRequest, HttpResponse, ResponseError, web};
use actix_web::http::StatusCode;
use utoipa::ToSchema;
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
use crate::server_state::ServerState;
#[derive(Serialize, ToSchema)]
pub struct StatusAlertResponse {
pub success: bool,
/// If there's an alert, this will be set.
/// The sub keys are optional, but at least one of them will be set.
/// i.e. we can have an alert with no message or no predefined category.
pub maybe_alert: Option<StatusAlertInfo>,
}
#[derive(Serialize, ToSchema)]
pub struct StatusAlertInfo {
/// If an alert is set, this might be a key for a common i18n message to show.
pub maybe_category: Option<StatusAlertCategory>,
/// We can set an optional message to show to the frontend.
///
/// This field may or may not be set. If it's set, this is custom text that should
/// be displayed to the user. For example, we might tell the user when we'll be
/// back online, etc. Both this field and `maybe_category` are optional.
///
/// If `maybe_category` is present, it should be a key for a predefined and
/// internationalized (i18n) message stored on the frontend.
///
/// Either or both of these fields may be set.
pub maybe_message: Option<String>,
}
#[derive(Serialize, ToSchema, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum StatusAlertCategory {
/// System is down for maintenance
DownForMaintenance,
}
#[derive(Debug, Serialize, ToSchema)]
pub enum StatusAlertError {
ServerError,
}
impl ResponseError for StatusAlertError {
fn status_code(&self) -> StatusCode {
match *self {
StatusAlertError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
serialize_as_json_error(self)
}
}
// NB: Not using derive_more::Display since Clion doesn't understand it.
impl std::fmt::Display for StatusAlertError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[utoipa::path(
get,
path = "/v1/status_alert_check",
responses(
(
status = 200,
description = "Check service status for frontend alert messages",
body = StatusAlertResponse,
),
(status = 500, description = "Server error", body = StatusAlertError)
),
)]
pub async fn status_alert_handler(
_http_request: HttpRequest,
server_state: web::Data<Arc<ServerState>>
) -> Result<HttpResponse, StatusAlertError> {
let maybe_category = server_state
.flags
.maybe_status_alert_category
.as_deref()
.map(|category| category_to_enum(&category))
.flatten();
let maybe_message = server_state
.flags
.maybe_status_alert_custom_message
.as_deref()
.map(|message| message.trim().to_string());
let maybe_alert = match (maybe_category, maybe_message) {
(None, None) => None,
(category, message) => Some(StatusAlertInfo {
maybe_category: category,
maybe_message: message,
}),
};
let response = StatusAlertResponse {
success: true,
maybe_alert,
};
let body = serde_json::to_string(&response)
.map_err(|e| StatusAlertError::ServerError)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
fn category_to_enum(category: &str) -> Option<StatusAlertCategory> {
let key = category.trim();
match key {
"down_for_maintenance" => Some(StatusAlertCategory::DownForMaintenance),
_ => None,
}
}
@@ -24,9 +24,7 @@ use http_server_common::request::get_request_ip::get_request_ip;
use migration::text_to_speech::get_tts_model_for_enqueue_inference_migration::TtsModelForEnqueueInferenceMigrationWrapper;
use mysql_queries::payloads::generic_inference_args::generic_inference_args::{GenericInferenceArgs, InferenceCategoryAbbreviated};
use mysql_queries::queries::generic_inference::web::insert_generic_inference_job::{insert_generic_inference_job, InsertGenericInferenceArgs};
use mysql_queries::queries::tts::tts_inference_jobs::insert_tts_inference_job::TtsInferenceJobInsertBuilder;
use redis_common::redis_keys::RedisKeys;
use tokens::tokens::tts_inference_jobs::TtsInferenceJobToken;
use tokens::tokens::users::UserToken;
use tts_common::priority::{FAKEYOU_DEFAULT_VALID_API_TOKEN_PRIORITY_LEVEL, FAKEYOU_INVESTOR_PRIORITY_LEVEL};
use user_input_common::check_for_slurs::contains_slurs;
@@ -55,10 +53,6 @@ const STORYTELLER_DEMO_COOKIE_NAME : &str = "storyteller_demo";
///
const DEBUG_HEADER_NAME : &str = "enable-debug-mode";
/// Requests with this header can be forced onto the new jobs system.
/// It'll be used to help test TT2 on the new system before it is rolled out for everyone.
const NEW_JOB_SYSTEM_HEADER_NAME : &str = "new-job-system";
/// The routing tag header can send workloads to particular k8s hosts.
/// This is useful for catching the live logs or intercepting the job.
/// NB: This is only for the new job system.
@@ -234,9 +228,6 @@ pub async fn enqueue_infer_tts_handler(
let is_debug_request = get_request_header_optional(&http_request, DEBUG_HEADER_NAME)
.is_some();
let use_new_job_system_for_request = get_request_header_optional(&http_request, NEW_JOB_SYSTEM_HEADER_NAME)
.is_some();
let maybe_routing_tag=
get_request_header_optional(&http_request, ROUTING_TAG_HEADER_NAME)
.map(|routing_tag| routing_tag.trim().to_string());
@@ -377,95 +368,55 @@ pub async fn enqueue_infer_tts_handler(
// ==================== ENQUEUE APPROPRIATE TTS TYPE ==================== //
let use_new_job_system = use_new_job_system_for_request || server_state.flags.enable_enqueue_generic_tts_job;
let job_token;
let job_token_type;
let model_type = InferenceModelType::Tacotron2;
let max_duration_seconds = plan.tts_max_duration_seconds();
if use_new_job_system {
// This branch uses the `generic-inference-job` service and tables.
info!("Creating tts inference job record (new generic job system)...");
// This branch uses the `generic-inference-job` service and tables.
info!("Creating tts inference job record (new generic job system)...");
let maybe_creator_user_token_typed = maybe_user_token
.as_deref()
.map(|token| UserToken::new_from_str(token));
let maybe_creator_user_token_typed = maybe_user_token
.as_deref()
.map(|token| UserToken::new_from_str(token));
let maybe_avt_token = server_state.avt_cookie_manager.get_avt_token_from_request(&http_request);
let maybe_avt_token = server_state.avt_cookie_manager.get_avt_token_from_request(&http_request);
let query_result = insert_generic_inference_job(InsertGenericInferenceArgs {
uuid_idempotency_token: &request.uuid_idempotency_token,
job_type: InferenceJobType::Tacotron2,
inference_category: InferenceCategory::TextToSpeech,
maybe_model_type: Some(model_type),
maybe_model_token: Some(request.tts_model_token.as_str()),
maybe_input_source_token: None, // NB: TTS doesn't have input media
maybe_input_source_token_type: None, // NB: TTS doesn't have input media
maybe_download_url: None,
maybe_cover_image_media_file_token: None,
maybe_raw_inference_text: Some(inference_text.as_str()),
maybe_max_duration_seconds: Some(max_duration_seconds),
maybe_inference_args: Some(GenericInferenceArgs {
inference_category: Some(InferenceCategoryAbbreviated::TextToSpeech),
args: None, // NB: We don't need to encode any args yet.
}),
maybe_creator_user_token: maybe_creator_user_token_typed.as_ref(),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: set_visibility,
priority_level,
requires_keepalive: false, // TODO: We may want this to be the case in the future.
is_debug_request,
maybe_routing_tag: maybe_routing_tag.as_deref(),
mysql_pool: &server_state.mysql_pool,
}).await;
let query_result = insert_generic_inference_job(InsertGenericInferenceArgs {
uuid_idempotency_token: &request.uuid_idempotency_token,
job_type: InferenceJobType::Tacotron2,
inference_category: InferenceCategory::TextToSpeech,
maybe_model_type: Some(model_type),
maybe_model_token: Some(request.tts_model_token.as_str()),
maybe_input_source_token: None, // NB: TTS doesn't have input media
maybe_input_source_token_type: None, // NB: TTS doesn't have input media
maybe_download_url: None,
maybe_cover_image_media_file_token: None,
maybe_raw_inference_text: Some(inference_text.as_str()),
maybe_max_duration_seconds: Some(max_duration_seconds),
maybe_inference_args: Some(GenericInferenceArgs {
inference_category: Some(InferenceCategoryAbbreviated::TextToSpeech),
args: None, // NB: We don't need to encode any args yet.
}),
maybe_creator_user_token: maybe_creator_user_token_typed.as_ref(),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: set_visibility,
priority_level,
requires_keepalive: false, // TODO: We may want this to be the case in the future.
is_debug_request,
maybe_routing_tag: maybe_routing_tag.as_deref(),
mysql_pool: &server_state.mysql_pool,
}).await;
let inference_job_token = match query_result {
Ok((inference_job_token, _id)) => inference_job_token,
Err(err) => {
warn!("New (generic) tts inference job creation DB error: {:?}", err);
return Err(InferTtsError::ServerError);
}
};
job_token = inference_job_token.to_string();
job_token_type = InferenceJobTokenType::Generic;
} else {
// This branch uses the `tts-inference-job` service and tables.
info!("Creating tts inference job record (legacy job system)...");
// This token is returned to the client.
job_token = TtsInferenceJobToken::generate().to_string();
let query_result = TtsInferenceJobInsertBuilder::new_for_fakeyou_request()
.set_job_token(&job_token)
.set_uuid_idempotency_token(&request.uuid_idempotency_token)
.set_model_token(&request.tts_model_token)
.set_raw_inference_text(&inference_text)
.set_maybe_creator_user_token(maybe_user_token.as_deref())
.set_creator_ip_address(&ip_address)
.set_creator_set_visibility(set_visibility.to_str())
.set_priority_level(priority_level)
.set_max_duration_seconds(max_duration_seconds)
.set_is_from_api(is_from_api)
.set_is_debug_request(is_debug_request)
.insert(&server_state.mysql_pool)
.await;
match query_result {
Ok(_) => {},
Err(err) => {
warn!("New tts inference job creation DB error: {:?}", err);
return Err(InferTtsError::ServerError);
}
let inference_job_token = match query_result {
Ok((inference_job_token, _id)) => inference_job_token,
Err(err) => {
warn!("New (generic) tts inference job creation DB error: {:?}", err);
return Err(InferTtsError::ServerError);
}
};
job_token_type = InferenceJobTokenType::LegacyTts;
}
let job_token = inference_job_token.to_string();
server_state.firehose_publisher.enqueue_tts_inference(maybe_user_token.as_deref(), &job_token, &request.tts_model_token)
.await
@@ -476,8 +427,8 @@ pub async fn enqueue_infer_tts_handler(
let response = InferTtsSuccessResponse {
success: true,
inference_job_token: job_token.to_string(),
inference_job_token_type: job_token_type,
inference_job_token: job_token,
inference_job_token_type: InferenceJobTokenType::Generic,
};
let body = serde_json::to_string(&response)
@@ -134,7 +134,7 @@ pub async fn list_voice_conversion_models_handler(
})?;
// TODO: Fail open in case the DB is down. Pull from expired cache if query fails.
let models = get_all_models(&mut mysql_connection, server_state.flags.switch_voice_conversion_to_model_weights)
let models = get_all_models(&mut mysql_connection, true)
.await
.map_err(|e| {
error!("Error querying database: {:?}", e);
@@ -27,10 +27,10 @@ use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::users::UserToken;
use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session;
use crate::configs::plans::plan_category::{PlanCategory};
use crate::configs::plans::plan_category::PlanCategory;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::server_state::ServerState;
use crate::util::allowed_studio_access::allowed_studio_access;
use crate::util::allowed_video_style_transfer_access::allowed_video_style_transfer_access;
use crate::validations::validate_idempotency_token_format::validate_idempotency_token_format;
/// Debug requests can get routed to special "debug-only" workers, which can
@@ -144,8 +144,8 @@ pub async fn enqueue_comfy_ui_handler(
// ==================== FEATURE FLAG CHECK ==================== //
if !allowed_studio_access(maybe_user_session.as_ref(), &server_state.flags) {
warn!("Storyteller Studio access is not permitted for user");
if !allowed_video_style_transfer_access(maybe_user_session.as_ref(), &server_state.flags) {
warn!("Video style transfer access is not permitted for user");
return Err(EnqueueComfyError::NotAuthorized);
}
@@ -340,7 +340,11 @@ async fn main() -> AnyhowResult<()> {
.ok_or(anyhow!("invalid server environment"))?;
let service_feature_flags = StaticFeatureFlags {
// Permanent (control plane / safety) flags
// Permanent (control plane / safety) flags : messaging
maybe_status_alert_category: easyenv::get_env_string_optional("FF_STATUS_ALERT_CATEGORY"),
maybe_status_alert_custom_message: easyenv::get_env_string_optional("FF_STATUS_ALERT_CUSTOM_MESSAGE"),
// Permanent (control plane / safety) flags : disabling features
global_429_pushback_filter_enabled: easyenv::get_env_bool_or_default("FF_GLOBAL_429_PUSHBACK_FILTER_ENABLED", false),
disable_unified_queue_stats_endpoint: easyenv::get_env_bool_or_default("FF_DISABLE_QUEUE_STATS_ENDPOINT", false),
disable_inference_queue_length_endpoint: easyenv::get_env_bool_or_default("FF_DISABLE_INFERENCE_QUEUE_LENGTH_ENDPOINT", false),
@@ -357,10 +361,9 @@ async fn main() -> AnyhowResult<()> {
troll_ban_user_percent: easyenv::get_env_num("FF_TROLL_BANNED_USER_PERCENT", 0)?,
// Temporary flags
enable_enqueue_generic_tts_job: easyenv::get_env_bool_or_default("FF_ENABLE_ENQUEUE_GENERIC_TTS_JOB", false),
switch_voice_conversion_to_model_weights: easyenv::get_env_bool_or_default("FF_SWITCH_VOICE_CONVERSION_TO_MODEL_WEIGHTS", false),
switch_tts_to_model_weights: easyenv::get_env_bool_or_default("FF_SWITCH_TTS_TO_MODEL_WEIGHTS", false),
force_session_studio_flags: easyenv::get_env_bool_or_default("FF_FORCE_SESSION_STUDIO_FLAG", false),
force_session_video_style_transfer_flags: easyenv::get_env_bool_or_default("FF_FORCE_SESSION_VST_FLAG", false),
};
let third_party_url_redirector = ThirdPartyUrlRedirector::new(server_environment);
@@ -85,6 +85,7 @@ use crate::http_server::endpoints::moderation::users::list_users::list_users_han
use crate::http_server::endpoints::prompts::get_prompt::get_prompt_handler;
use crate::http_server::endpoints::service::health_check_handler::get_health_check_handler;
use crate::http_server::endpoints::service::public_info_handler::get_public_info_handler;
use crate::http_server::endpoints::service::status_alert_handler::status_alert_handler;
use crate::http_server::endpoints::stats::get_unified_queue_stats::get_unified_queue_stats_handler;
use crate::http_server::endpoints::stubs::app_model_downloads::get_app_model_downloads_handler;
use crate::http_server::endpoints::stubs::app_news::get_app_news_handler;
@@ -270,6 +271,7 @@ pub fn add_routes<T, B> (app: App<T>, server_environment: ServerEnvironment) ->
.into_app();
// =================== BVH from Workflow ====================
let mut app = RouteBuilder::from_app(app)
.add_post("/v1/conversion/enqueue_bvh_to_workflow", enqueue_bvh_to_workflow_handler)
.into_app();
@@ -280,6 +282,12 @@ pub fn add_routes<T, B> (app: App<T>, server_environment: ServerEnvironment) ->
.add_get("/v1/stats/queues", get_unified_queue_stats_handler)
.into_app();
// ==================== Service Status ====================
let mut app = RouteBuilder::from_app(app)
.add_get("/v1/status_alert_check", status_alert_handler)
.into_app();
// ==================== COMPONENTS ====================
app = add_suggested_api_v1_account_creation_and_session_routes(app); // /create_account, /session, /login, /logout
@@ -223,6 +223,12 @@ pub struct StaticFeatureFlags {
/// Used to bring the service back online slowly.
pub global_429_pushback_filter_enabled: bool,
/// If we're suffering an outage, set the alert category to display a predefined message to users.
pub maybe_status_alert_category: Option<String>,
/// If we're suffering an outage, set custom text for the alert message.
pub maybe_status_alert_custom_message: Option<String>,
/// Disable the live `/v1/stats/queues` endpoint for all users and serve a static value instead.
pub disable_unified_queue_stats_endpoint: bool,
@@ -254,16 +260,6 @@ pub struct StaticFeatureFlags {
/// This should be a number over 100.
pub troll_ban_user_percent: u8,
// TODO(2023-03-20): Remove temporary flag when done.
// NB(bt,2023-12-18): This is rolled out (=="TRUE")
/// TEMPORARY: Control enqueuing TTS jobs to the generic job worker.
pub enable_enqueue_generic_tts_job: bool,
// TODO(2023-12-18): Remove temporary flag when done.
/// TEMPORARY: Move voice control model listing over to `model_weights` from `voice_conversion_models`
/// This will control all downstream enqueuing, jobs, etc.
pub switch_voice_conversion_to_model_weights: bool,
// TODO(2024-01-13): Remove temporary flag when done.
/// TEMPORARY: Move voice control model listing over to `model_weights` from `tts_models`
/// This will control all downstream enqueuing, jobs, etc.
@@ -273,6 +269,11 @@ pub struct StaticFeatureFlags {
/// TEMPORARY: Require users to have a studio flag on their `users` table record to use studio features.
/// This will also prevent anonymous users from using the service APIs marked as studio-only.
pub force_session_studio_flags: bool,
// TODO(2024-03-05): Remove when done.
/// TEMPORARY: Require users to have a flag on their `users` table record to use video style transfer.
/// This will also prevent anonymous users from using the service APIs marked as VST-only.
pub force_session_video_style_transfer_flags: bool,
}
/// Instead of top level service denial, these are bans against entities that instead return
@@ -0,0 +1,52 @@
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
use users_component::utils::user_session_extended::UserSessionExtended;
use users_component::utils::user_session_feature_flags::UserSessionFeatureFlags;
use crate::server_state::StaticFeatureFlags;
/// Check whether we should allow the request access to explore media.
pub fn allowed_explore_media_access(maybe_session: Option<impl UserSessionExploreMediaFlag>) -> bool {
// NB: We're more careful here since this is sensitive until we have NLP checks.
maybe_session
.map(|ref session| session.can_access_explore_media())
.unwrap_or(false)
}
pub trait UserSessionExploreMediaFlag {
fn can_access_explore_media(&self) -> bool;
}
impl UserSessionExploreMediaFlag for UserSessionExtended {
fn can_access_explore_media(&self) -> bool {
self.feature_flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia)
}
}
impl UserSessionExploreMediaFlag for &UserSessionExtended {
fn can_access_explore_media(&self) -> bool {
self.feature_flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia)
}
}
impl UserSessionExploreMediaFlag for SessionUserRecord {
fn can_access_explore_media(&self) -> bool {
user_session_has_feature(self)
}
}
impl UserSessionExploreMediaFlag for &SessionUserRecord {
fn can_access_explore_media(&self) -> bool {
user_session_has_feature(self)
}
}
fn user_session_has_feature(user_session: &SessionUserRecord) -> bool {
// TODO(bt, 2024-03-05): this is horrible.
// There should be a wrapper class between the query and the caller.
let flags = UserSessionFeatureFlags {
maybe_feature_flags: user_session.maybe_feature_flags.clone(),
};
flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia)
}
@@ -1,5 +1,7 @@
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
use users_component::utils::user_session_extended::UserSessionExtended;
use users_component::utils::user_session_feature_flags::UserSessionFeatureFlags;
use crate::server_state::StaticFeatureFlags;
@@ -20,24 +22,44 @@ pub trait UserSessionStudioFlag {
impl UserSessionStudioFlag for UserSessionExtended {
fn can_access_studio(&self) -> bool {
self.role.can_access_studio
self.role.can_access_studio || self.feature_flags.has_permission_unoptimized(UserFeatureFlag::Studio)
}
}
impl UserSessionStudioFlag for &UserSessionExtended {
fn can_access_studio(&self) -> bool {
self.role.can_access_studio
self.role.can_access_studio || self.feature_flags.has_permission_unoptimized(UserFeatureFlag::Studio)
}
}
impl UserSessionStudioFlag for SessionUserRecord {
fn can_access_studio(&self) -> bool {
self.can_access_studio
if self.can_access_studio {
return true
}
// TODO(bt, 2024-03-05): this is horrible.
// There should be a wrapper class between the query and the caller.
let flags = UserSessionFeatureFlags {
maybe_feature_flags: self.maybe_feature_flags.clone(),
};
flags.has_permission_unoptimized(UserFeatureFlag::Studio)
}
}
impl UserSessionStudioFlag for &SessionUserRecord {
fn can_access_studio(&self) -> bool {
self.can_access_studio
if self.can_access_studio {
return true
}
// TODO(bt, 2024-03-05): this is horrible.
// There should be a wrapper class between the query and the caller.
let flags = UserSessionFeatureFlags {
maybe_feature_flags: self.maybe_feature_flags.clone(),
};
flags.has_permission_unoptimized(UserFeatureFlag::Studio)
}
}
@@ -0,0 +1,55 @@
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
use users_component::utils::user_session_extended::UserSessionExtended;
use users_component::utils::user_session_feature_flags::UserSessionFeatureFlags;
use crate::server_state::StaticFeatureFlags;
/// Check whether we should allow the request access to video style transfer
pub fn allowed_video_style_transfer_access(maybe_session: Option<impl UserSessionVideoStyleTransferFlag>, flags: &StaticFeatureFlags) -> bool {
if !flags.force_session_video_style_transfer_flags {
return true;
}
maybe_session
.map(|ref session| session.can_access_video_style_transfer())
.unwrap_or(false)
}
pub trait UserSessionVideoStyleTransferFlag {
fn can_access_video_style_transfer(&self) -> bool;
}
impl UserSessionVideoStyleTransferFlag for UserSessionExtended {
fn can_access_video_style_transfer(&self) -> bool {
self.feature_flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer)
}
}
impl UserSessionVideoStyleTransferFlag for &UserSessionExtended {
fn can_access_video_style_transfer(&self) -> bool {
self.feature_flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer)
}
}
impl UserSessionVideoStyleTransferFlag for SessionUserRecord {
fn can_access_video_style_transfer(&self) -> bool {
user_session_has_feature(self)
}
}
impl UserSessionVideoStyleTransferFlag for &SessionUserRecord {
fn can_access_video_style_transfer(&self) -> bool {
user_session_has_feature(self)
}
}
fn user_session_has_feature(user_session: &SessionUserRecord) -> bool {
// TODO(bt, 2024-03-05): this is horrible.
// There should be a wrapper class between the query and the caller.
let flags = UserSessionFeatureFlags {
maybe_feature_flags: user_session.maybe_feature_flags.clone(),
};
flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer)
}
@@ -1,5 +1,7 @@
pub mod allowed_explore_media_access;
pub mod allowed_studio_access;
pub mod allowed_video_style_transfer_access;
pub mod delete_role_disambiguation;
pub mod encrypted_sort_id;
pub mod placeholder_images;
pub mod troll_user_bans;
pub mod troll_user_bans;