mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-27 12:10:08 -04:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd95aaff8c | ||
|
|
8afd10c864 | ||
|
|
0e62fe9d08 | ||
|
|
dda2e0840d | ||
|
|
b26276cfda | ||
|
|
f327dfcdbb | ||
|
|
f4176bf84a | ||
|
|
43b6808f23 | ||
|
|
330f24413d | ||
|
|
af6370eea1 | ||
|
|
3a4ca7b548 | ||
|
|
f7d5ad496c | ||
|
|
f76928d001 | ||
|
|
32cfb10323 | ||
|
|
08d3c389b9 | ||
|
|
1a3b02b5d2 | ||
|
|
024b4d769c | ||
|
|
4993410740 | ||
|
|
f99b5a92b8 | ||
|
|
130e0daa08 | ||
|
|
cda690773f | ||
|
|
91d0a1da8f | ||
|
|
8c7b100150 | ||
|
|
ae5eea53fe | ||
|
|
241f648baf | ||
|
|
e4756a4f2c | ||
|
|
9c8ee8dbbb | ||
|
|
8072d13596 | ||
|
|
1d937ab981 | ||
|
|
496ccd68f1 | ||
|
|
72fdd75568 | ||
|
|
fba7d2a1eb | ||
|
|
347fe685cd | ||
|
|
32331f9890 | ||
|
|
65d6f178bc | ||
|
|
d9d166e500 |
Generated
+1
@@ -2215,6 +2215,7 @@ dependencies = [
|
||||
"sqlx-mysql",
|
||||
"strum",
|
||||
"toml",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -2,4 +2,5 @@ pub mod media_files;
|
||||
pub mod media_uploads;
|
||||
pub mod public_path;
|
||||
pub mod voice_conversion_results;
|
||||
pub mod zs_voices;
|
||||
pub mod zs_voices;
|
||||
pub mod weight_uploads;
|
||||
@@ -0,0 +1,77 @@
|
||||
use crockford::crockford_entropy_lower;
|
||||
// TODO Thoughts NOTE:
|
||||
// I am wondering if it be easier if we just had a single struct and impl to handle downloads from public and private buckets.
|
||||
// essentially, the only difference is the prefix and file postfix and the path of where the file goes and the path will be constructed from there.
|
||||
// then you can tie in the downloader as a single object that can be used for both public and private buckets.
|
||||
|
||||
use crate::public::public_path::PublicPath;
|
||||
use crate::util::hashed_directory_path_long_string::hashed_directory_path_long_string;
|
||||
|
||||
const WEIGHT_UPLOAD_DIRECTORY : &str = "/weight_upload";
|
||||
|
||||
/// Directory for user media uploads.
|
||||
/// Each uploaded file gets its own directory so that we can store the original
|
||||
/// file alongside re-processed derivatives.
|
||||
#[derive(Clone)]
|
||||
pub struct WeightUploadDirectory {
|
||||
object_hash: String,
|
||||
directory: String,
|
||||
}
|
||||
|
||||
impl PublicPath for WeightUploadDirectory {}
|
||||
|
||||
impl WeightUploadDirectory {
|
||||
|
||||
pub fn generate_new() -> Self {
|
||||
let entropy = crockford_entropy_lower(32);
|
||||
Self::from_object_hash(&entropy)
|
||||
}
|
||||
|
||||
pub fn from_object_hash(object_hash: &str) -> Self {
|
||||
// TODO: Path construction could be cleaner.
|
||||
let middle = hashed_directory_path_long_string(object_hash);
|
||||
let directory = format!("{}/{}{}", WEIGHT_UPLOAD_DIRECTORY, middle, object_hash);
|
||||
Self {
|
||||
object_hash: object_hash.to_string(),
|
||||
directory,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_directory_path_str(&self) -> &str {
|
||||
&self.directory
|
||||
}
|
||||
|
||||
pub fn get_object_hash(&self) -> &str {
|
||||
&self.object_hash
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::public::weight_uploads::directory::WeightUploadDirectory;
|
||||
|
||||
#[test]
|
||||
pub fn generate_new_entropy() {
|
||||
let directory = WeightUploadDirectory::generate_new();
|
||||
assert_eq!(directory.get_object_hash().len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn get_directory_path_str() {
|
||||
let directory = WeightUploadDirectory::from_object_hash("abcdefghijk");
|
||||
assert_eq!(directory.get_directory_path_str(), "/weight_upload/a/b/c/d/e/abcdefghijk");
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn get_directory_path_str_short_name() {
|
||||
let directory = WeightUploadDirectory::from_object_hash("foo");
|
||||
assert_eq!(directory.get_directory_path_str(), "/weight_upload/f/o/foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn get_object_hash() {
|
||||
let hash = "abcdefghijk";
|
||||
let directory = WeightUploadDirectory::from_object_hash(hash);
|
||||
assert_eq!(directory.get_object_hash(), hash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod directory;
|
||||
pub mod original_file;
|
||||
@@ -0,0 +1,99 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crockford::crockford_entropy_lower;
|
||||
|
||||
use crate::public::weight_uploads::directory::WeightUploadDirectory;
|
||||
use crate::public::public_path::PublicPath;
|
||||
|
||||
const ORIGINAL_FILE_BASENAME : &str = "original_upload.bin";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WeightUploadOriginalFilePath {
|
||||
directory: WeightUploadDirectory,
|
||||
full_object_path: String,
|
||||
}
|
||||
|
||||
impl PublicPath for WeightUploadOriginalFilePath {}
|
||||
|
||||
impl WeightUploadOriginalFilePath {
|
||||
|
||||
pub fn generate_new() -> Self {
|
||||
let entropy = crockford_entropy_lower(32);
|
||||
Self::from_object_hash(&entropy)
|
||||
}
|
||||
|
||||
pub fn from_object_hash(hash: &str) -> Self {
|
||||
// TODO: Path construction could be cleaner.
|
||||
let directory = WeightUploadDirectory::from_object_hash(hash);
|
||||
let full_object_path = format!("{}/{}", directory.get_directory_path_str(), ORIGINAL_FILE_BASENAME);
|
||||
Self {
|
||||
directory,
|
||||
full_object_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_full_object_path_str(&self) -> &str {
|
||||
&self.full_object_path
|
||||
}
|
||||
|
||||
pub fn to_full_object_pathbuf(&self) -> PathBuf {
|
||||
PathBuf::from(&self.full_object_path)
|
||||
}
|
||||
|
||||
pub fn get_directory(&self) -> &WeightUploadDirectory {
|
||||
&self.directory
|
||||
}
|
||||
|
||||
pub fn get_object_hash(&self) -> &str {
|
||||
&self.directory.get_object_hash()
|
||||
}
|
||||
|
||||
pub fn get_basename() -> &'static str {
|
||||
ORIGINAL_FILE_BASENAME
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
use crate::public::weight_uploads::original_file::WeightUploadOriginalFilePath;
|
||||
|
||||
#[test]
|
||||
pub fn generate_new_entropy() {
|
||||
let file = WeightUploadOriginalFilePath::generate_new();
|
||||
assert_eq!(file.get_object_hash().len(), 32);
|
||||
assert_eq!(file.get_directory().get_object_hash().len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn get_full_object_path_str() {
|
||||
let file = WeightUploadOriginalFilePath::from_object_hash("abcdefghijk");
|
||||
assert_eq!(file.get_full_object_path_str(), "/weight_upload/a/b/c/d/e/abcdefghijk/original_upload.bin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn to_full_object_pathbuf() {
|
||||
let file = WeightUploadOriginalFilePath::from_object_hash("abcdefghijk");
|
||||
assert_eq!(file.to_full_object_pathbuf(), PathBuf::from("/weight_upload/a/b/c/d/e/abcdefghijk/original_upload.bin"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn get_full_object_path_str_short_name() {
|
||||
let file = WeightUploadOriginalFilePath::from_object_hash("foo");
|
||||
assert_eq!(file.get_full_object_path_str(), "/weight_upload/f/o/foo/original_upload.bin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn get_full_object_path_str_starts_with_directory() {
|
||||
let file = WeightUploadOriginalFilePath::from_object_hash("abcdefghijk");
|
||||
let full_path = file.get_full_object_path_str();
|
||||
assert!(full_path.starts_with(file.get_directory().get_directory_path_str()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn get_object_hash() {
|
||||
let hash = "abcdefghijk";
|
||||
let file = WeightUploadOriginalFilePath::from_object_hash(hash);
|
||||
assert_eq!(file.get_object_hash(), hash);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ serde_derive = "1.0.125"
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "sqlite", "runtime-tokio-native-tls", "chrono" ] }
|
||||
sqlx-core = { version = "0.7.2" }
|
||||
sqlx-mysql= { version = "0.7.2" }
|
||||
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
|
||||
|
||||
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
|
||||
utoipa = { version = "4", features = ["chrono","actix_extras"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ pub enum MediaUploadType {
|
||||
|
||||
/// Video files: mp4, etc.
|
||||
Video,
|
||||
|
||||
// Binary files: safetensors ... weights, etc.
|
||||
Binary,
|
||||
}
|
||||
|
||||
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
|
||||
@@ -33,6 +36,7 @@ impl MediaUploadType {
|
||||
Self::Audio => "audio",
|
||||
Self::Image => "image",
|
||||
Self::Video => "video",
|
||||
Self::Binary => "binary",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +45,7 @@ impl MediaUploadType {
|
||||
"audio" => Ok(Self::Audio),
|
||||
"image" => Ok(Self::Image),
|
||||
"video" => Ok(Self::Video),
|
||||
"binary" => Ok(Self::Binary),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
@@ -56,6 +61,7 @@ mod tests {
|
||||
assert_serialization(MediaUploadType::Audio, "audio");
|
||||
assert_serialization(MediaUploadType::Image, "image");
|
||||
assert_serialization(MediaUploadType::Video, "video");
|
||||
assert_serialization(MediaUploadType::Binary, "binary");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -63,6 +69,7 @@ mod tests {
|
||||
assert_eq!(MediaUploadType::Audio.to_str(), "audio");
|
||||
assert_eq!(MediaUploadType::Image.to_str(), "image");
|
||||
assert_eq!(MediaUploadType::Video.to_str(), "video");
|
||||
assert_eq!(MediaUploadType::Binary.to_str(), "binary");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -70,6 +77,7 @@ mod tests {
|
||||
assert_eq!(MediaUploadType::from_str("audio").unwrap(), MediaUploadType::Audio);
|
||||
assert_eq!(MediaUploadType::from_str("image").unwrap(), MediaUploadType::Image);
|
||||
assert_eq!(MediaUploadType::from_str("video").unwrap(), MediaUploadType::Video);
|
||||
assert_eq!(MediaUploadType::from_str("binary").unwrap(), MediaUploadType::Binary);
|
||||
assert!(MediaUploadType::from_str("foo").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod weights_types;
|
||||
pub mod weights_category;
|
||||
pub mod weights_file_type;
|
||||
@@ -5,8 +5,10 @@ use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
|
||||
|
||||
use utoipa::ToSchema;
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema)]
|
||||
pub enum WeightsCategory {
|
||||
#[serde(rename = "image_generation")]
|
||||
ImageGeneration,
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
// TODO we will need to scan the checkpoints for malicious code. We can't just trust the file extension.
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
|
||||
pub enum WeightsFileType {
|
||||
#[serde(rename = "checkpoint")]
|
||||
Checkpoint,
|
||||
#[serde(rename = "safetensors")]
|
||||
SafeTensors,
|
||||
}
|
||||
|
||||
impl WeightsFileType {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Checkpoint => "checkpoint",
|
||||
Self::SafeTensors => "safetensors",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"checkpoint" => Ok(Self::Checkpoint),
|
||||
"safetensors" => Ok(Self::SafeTensors),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_variants() -> BTreeSet<Self> {
|
||||
BTreeSet::from([
|
||||
Self::Checkpoint,
|
||||
Self::SafeTensors,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl_enum_display_and_debug_using_to_str!(WeightsFileType);
|
||||
impl_mysql_enum_coders!(WeightsFileType);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_to_str() {
|
||||
assert_eq!(WeightsFileType::Checkpoint.to_str(), "checkpoint");
|
||||
assert_eq!(WeightsFileType::SafeTensors.to_str(), "safetensors");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
assert_eq!(WeightsFileType::from_str("checkpoint").unwrap(), WeightsFileType::Checkpoint);
|
||||
assert_eq!(WeightsFileType::from_str("safetensors").unwrap(), WeightsFileType::SafeTensors);
|
||||
assert!(WeightsFileType::from_str("invalid").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_variants() {
|
||||
let variants = WeightsFileType::all_variants();
|
||||
assert_eq!(variants.len(), 2);
|
||||
assert!(variants.contains(&WeightsFileType::Checkpoint));
|
||||
assert!(variants.contains(&WeightsFileType::SafeTensors));
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ use std::collections::BTreeSet;
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize,ToSchema)]
|
||||
pub enum WeightsType {
|
||||
#[serde(rename = "hifigan_tt2")]
|
||||
HifiganTacotron2,
|
||||
@@ -23,6 +23,8 @@ pub enum WeightsType {
|
||||
Tacotron2,
|
||||
#[serde(rename = "loRA")]
|
||||
LoRA,
|
||||
#[serde(rename = "vall_e")]
|
||||
VallE,
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +38,7 @@ impl WeightsType {
|
||||
Self::SoVitsSvc => "so_vits_svc",
|
||||
Self::Tacotron2 => "tt2",
|
||||
Self::LoRA => "loRA",
|
||||
Self::VallE => "vall_e",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +51,7 @@ impl WeightsType {
|
||||
"so_vits_svc" => Ok(Self::SoVitsSvc),
|
||||
"tt2" => Ok(Self::Tacotron2),
|
||||
"loRA" => Ok(Self::LoRA),
|
||||
"vall_e" => Ok(Self::VallE),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
@@ -61,6 +65,7 @@ impl WeightsType {
|
||||
Self::SoVitsSvc,
|
||||
Self::Tacotron2,
|
||||
Self::LoRA,
|
||||
Self::VallE
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -82,6 +87,7 @@ mod tests {
|
||||
assert_eq!(WeightsType::SoVitsSvc.to_str(), "so_vits_svc");
|
||||
assert_eq!(WeightsType::Tacotron2.to_str(), "tt2");
|
||||
assert_eq!(WeightsType::LoRA.to_str(), "loRA");
|
||||
assert_eq!(WeightsType::VallE.to_str(), "vall_e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -93,6 +99,7 @@ mod tests {
|
||||
assert_eq!(WeightsType::from_str("so_vits_svc").unwrap(), WeightsType::SoVitsSvc);
|
||||
assert_eq!(WeightsType::from_str("tt2").unwrap(), WeightsType::Tacotron2);
|
||||
assert_eq!(WeightsType::from_str("loRA").unwrap(), WeightsType::LoRA);
|
||||
assert_eq!(WeightsType::from_str("vall_e").unwrap(), WeightsType::VallE);
|
||||
assert!(WeightsType::from_str("invalid").is_err());
|
||||
}
|
||||
|
||||
@@ -107,5 +114,6 @@ mod tests {
|
||||
assert!(variants.contains(&WeightsType::SoVitsSvc));
|
||||
assert!(variants.contains(&WeightsType::Tacotron2));
|
||||
assert!(variants.contains(&WeightsType::LoRA));
|
||||
assert!(variants.contains(&WeightsType::VallE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ use strum::EnumIter;
|
||||
/// See also: https://docs.rs/sqlx/0.4.0-beta.1/sqlx/trait.Type.html
|
||||
///
|
||||
/// *DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY!*
|
||||
///
|
||||
|
||||
use utoipa::ToSchema;
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Deserialize, Serialize, sqlx::Type)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Deserialize, Serialize, sqlx::Type,ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
pub enum Visibility {
|
||||
|
||||
@@ -24,7 +24,7 @@ sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls",
|
||||
sqlx-core = { version = "0.7.2" }
|
||||
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
|
||||
|
||||
utoipa = { version = "4", features = ["actix_extras"] }
|
||||
utoipa = { version = "4", features = ["chrono","actix_extras"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ use serde::Serialize;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
use utoipa::ToSchema;
|
||||
/// The primary key for the "model_weights" table.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize,ToSchema)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct ModelWeightToken(pub String);
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
use utoipa::ToSchema;
|
||||
/// The primary key for users.
|
||||
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize, Default)]
|
||||
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize, Default,ToSchema)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct UserToken(pub String);
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
/// The primary key for user weight uploads
|
||||
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct WeightUploadToken(pub String);
|
||||
|
||||
impl_string_token!(WeightUploadToken);
|
||||
impl_crockford_generator!(WeightUploadToken, 32usize, TokenPrefix::WeightUpload, CrockfordLower);
|
||||
@@ -56,7 +56,6 @@ pub async fn main() -> AnyhowResult<()> {
|
||||
seed_voice_conversion(&pool).await?;
|
||||
|
||||
seed_weights(&pool).await?;
|
||||
|
||||
|
||||
seed_tts_tacotron2(&pool, maybe_bucket_clients.as_ref()).await?;
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use std::vec;
|
||||
|
||||
use buckets::public::media_files::original_file;
|
||||
use log::info;
|
||||
|
||||
use tokens::tokens::users::UserToken;
|
||||
use sqlx::{ MySql, Pool };
|
||||
use enums::by_table::model_weights::{
|
||||
weights_types::WeightsType,
|
||||
weights_category::WeightsCategory,
|
||||
weights_types::{WeightsType, self},
|
||||
weights_category::{WeightsCategory, self},
|
||||
};
|
||||
use errors::{ anyhow, AnyhowResult };
|
||||
|
||||
@@ -15,6 +18,342 @@ use enums::common::visibility::Visibility;
|
||||
|
||||
use mysql_queries::queries::users::user::get_user_token_by_username::get_user_token_by_username;
|
||||
|
||||
pub async fn seed_weights_for_paging( mysql_pool: &Pool<MySql>,user_token: UserToken) -> AnyhowResult<()> {
|
||||
let sd1_5_markdown_description = r#"
|
||||
# Dragonfruit AI Models Update and Workflow
|
||||
|
||||
## Last Update for Dragonfruit Models
|
||||
- **Note:** This might be the last update for a while, focusing next on a realistic merge (WIP).
|
||||
- **Future Plans:** Possible update to Z.5D v2 after completing the realistic merge.
|
||||
|
||||
## New Models Announcement
|
||||
- **Upcoming Models:** Separate from the Dragonfruit branch, so stay tuned!
|
||||
|
||||
## DragonfruitGT v1 (Z v2)
|
||||
- **Focus:** More fantasy, richer color range, improved landscapes, more masculine men.
|
||||
- **Alternative:** Use DragonfruitOG v2.0 for thicker lines, simpler colors.
|
||||
|
||||
## GT v1.0 (Z v2.0) Features
|
||||
- Improved backgrounds
|
||||
- Deeper contrast and color
|
||||
- Bolder lines and more detail
|
||||
|
||||
### Settings for GT
|
||||
- Steps: 30-50
|
||||
- Clip skip: 2
|
||||
- Upscaler: Euler a / DPM++ 2M SDE Karras
|
||||
- Extensions: Adetailer, VAE: berrysmix
|
||||
|
||||
## Workflow for Images
|
||||
1. **BerryMix VAE:** Ensure correct color.
|
||||
2. **Adetailer Extension:** Improve faces, bodies, and hands. Also available on Civitai and Huggingface.
|
||||
3. **Hires Fix:** Denoising strength 0.3 - 0.35, using upscaler "x_NMKD-Superscale-SP_178000_G".
|
||||
4. **Img2img Transfer:** Use denoising strength 0.3-0.35 with the same prompt and settings. Adjust height after hires.
|
||||
5. **Second Img2img Transfer:** Low denoising strength 0.1-3.0. Use SD upscale script with "RealESRGAN_x4plus_anime_6B" sampler for size increase.
|
||||
|
||||
## DragonfruitZ.5D v1.0
|
||||
- **Style:** 2.5D model, merging DragonfruitZ with realistic elements.
|
||||
- **Features:** Skin color variety, masculine men, landscapes, fantasy, NSFW content, detailed faces.
|
||||
|
||||
### Settings for Z.5D
|
||||
- Steps: 30
|
||||
- Clip skip: 2
|
||||
- Upscaler: Euler a
|
||||
- Extensions: Adetailer, VAE: berrysmix
|
||||
|
||||
## DragonfruitOG v2.0
|
||||
- **Refinement:** Of the original Dragonfruit model.
|
||||
- **Style:** Bold, clean lines and bold colors with decreased detail for simplicity.
|
||||
- **Improvements:** Teeth, skin color variety, landscapes, fantasy elements, NSFW content.
|
||||
|
||||
## DragonfruitZ v1.0
|
||||
- **Alternative Version:** More fantasy and color range, better landscapes, more masculine men.
|
||||
- **Suggestion:** Use Dragonfruit (Unisex) v2 for thicker lines and simpler colors.
|
||||
|
||||
## Dragonfruit OG v1.0
|
||||
- **Focus:** More unisex, suitable for creating male characters.
|
||||
- **Personal Note:** First merge, aimed at 2D, flat, colorful, thick lined styles.
|
||||
|
||||
### Personal Settings
|
||||
- Steps: 20-30
|
||||
- Clip skip: 2
|
||||
- Upscaler: Euler a / DPM++ 2M SDE Karras
|
||||
- Extensions: Adetailer, VAE: berrysmix
|
||||
|
||||
*Thank you for your continued support and feedback!*
|
||||
"#;
|
||||
|
||||
let sd1_5_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/be706282-2978-42a0-aaa2-73881aad94e9/width=1024/00049-2287632957-1girl,face,curly%20hair,red%20hair,white%20background,.jpeg";
|
||||
|
||||
let user_token = user_token;
|
||||
|
||||
// create a loop that loops from 1 to 100
|
||||
for i in 1..=100 {
|
||||
// create a new weight
|
||||
|
||||
let weights_category = match i {
|
||||
1..=20 => WeightsCategory::VoiceConversion,
|
||||
21..=40 => WeightsCategory::TextToSpeech,
|
||||
41..=60 => WeightsCategory::ImageGeneration,
|
||||
61..=80 => WeightsCategory::ImageGeneration,
|
||||
81..=100 => WeightsCategory::ImageGeneration,
|
||||
_ => WeightsCategory::ImageGeneration
|
||||
};
|
||||
|
||||
let weights_types = match i {
|
||||
1..=20 => WeightsType::RvcV2,
|
||||
21..=40 => WeightsType::HifiganTacotron2,
|
||||
41..=60 => WeightsType::StableDiffusion15,
|
||||
61..=80 => WeightsType::StableDiffusionXL,
|
||||
81..=100 => WeightsType::LoRA,
|
||||
_ => WeightsType::LoRA
|
||||
};
|
||||
|
||||
let mut model_weight_token;
|
||||
let mut title;
|
||||
let mut description;
|
||||
let mut description_rendered_html;
|
||||
let mut original_filename;
|
||||
let mut original_download_url;
|
||||
let mut thumbnail_token;
|
||||
|
||||
let mut private_bucket_hash:String = "".to_string();
|
||||
let mut private_bucket_prefix;
|
||||
let mut private_bucket_extension;
|
||||
|
||||
let mut cached_user_ratings_total_count;
|
||||
let mut cached_user_ratings_positive_count;
|
||||
let mut cached_user_ratings_negative_count;
|
||||
let mut cached_user_ratings_ratio;
|
||||
let mut version;
|
||||
|
||||
match i {
|
||||
1..=20 => {
|
||||
// voice conversion model
|
||||
let rvc_markdown_description = r#"
|
||||
# RVC v2
|
||||
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
|
||||
- Gawr Gura MOS 4
|
||||
"#;
|
||||
|
||||
model_weight_token = ModelWeightToken(format!("{}", i));
|
||||
title = format!("Gawr Gura: {}", i);
|
||||
description = format!("Description Number {} The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.", i);
|
||||
description_rendered_html = rvc_markdown_description;
|
||||
|
||||
original_filename = format!("gura_gwar{}.safetensors", i);
|
||||
original_download_url = format!("www.google.ca");
|
||||
|
||||
// NOTE TO SELF this will be a join for later on for now they can use this.
|
||||
thumbnail_token = "";
|
||||
|
||||
// private_bucket_hash = format!("bucket_hash{}", i);
|
||||
private_bucket_prefix = format!("_fake");
|
||||
private_bucket_extension = format!("rvcV2");
|
||||
cached_user_ratings_total_count = i;
|
||||
cached_user_ratings_positive_count = i;
|
||||
cached_user_ratings_negative_count = i;
|
||||
cached_user_ratings_ratio = i as u32 / 100;
|
||||
version = i as i32;
|
||||
println!("Seeding RVCv2 model {}", i);
|
||||
},
|
||||
21..=40 =>{
|
||||
// TTS
|
||||
model_weight_token = ModelWeightToken(format!("{}", i));
|
||||
title = format!("dragonfruitGTv1: {}", i);
|
||||
|
||||
description = format!("Description Number {}:", i);
|
||||
|
||||
let tts_markdown_description = r#"
|
||||
# HifiGAN Tacotron2
|
||||
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
|
||||
- Goku MOS 4
|
||||
"#;
|
||||
|
||||
description_rendered_html = tts_markdown_description;
|
||||
|
||||
original_filename = format!("filename{}.txt", i);
|
||||
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
|
||||
|
||||
// NOTE TO SELF this will be a join for later on for now they can use this.
|
||||
thumbnail_token = "";
|
||||
|
||||
// private_bucket_hash = format!("bucket_hash{}", i);
|
||||
private_bucket_prefix = format!("_fake");
|
||||
private_bucket_extension = format!("tt2");
|
||||
|
||||
cached_user_ratings_total_count = i;
|
||||
cached_user_ratings_positive_count = i;
|
||||
cached_user_ratings_negative_count = i;
|
||||
cached_user_ratings_ratio = i as u32 / 100;
|
||||
version = i as i32;
|
||||
println!("Seeding TT2 model {}", i);
|
||||
},
|
||||
41..=60 => {
|
||||
// SD 1.5
|
||||
model_weight_token = ModelWeightToken(format!("{}", i));
|
||||
title = format!("dragonfruitGTv1: {}", i);
|
||||
|
||||
description = format!("Description Number {}:", i);
|
||||
|
||||
description_rendered_html = sd1_5_markdown_description;
|
||||
|
||||
original_filename = format!("filename{}.txt", i);
|
||||
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
|
||||
|
||||
// NOTE TO SELF this will be a join for later on for now they can use this.
|
||||
thumbnail_token = ""; //sd1_5_image_token;
|
||||
|
||||
// private_bucket_hash = format!("bucket_hash{}", i);
|
||||
private_bucket_prefix = format!("_fake");
|
||||
private_bucket_extension = format!("sd15");
|
||||
|
||||
cached_user_ratings_total_count = i;
|
||||
cached_user_ratings_positive_count = i;
|
||||
cached_user_ratings_negative_count = i;
|
||||
cached_user_ratings_ratio = i as u32 / 100;
|
||||
version = i as i32;
|
||||
println!("Seeding SD15 model {}", i);
|
||||
},
|
||||
61..=80 => {
|
||||
// SD XL
|
||||
let sdxl_markdown_description = r#"
|
||||
# Special Edition Release Announcement
|
||||
|
||||
## What's New?
|
||||
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
|
||||
|
||||
## Tips for Use
|
||||
1. **Simple Prompts:** Avoid adding words like "detailed, realistic..." or specifying a specific artist. Simply describe what you want to see.
|
||||
2. **CFG Scale:** A range between 3 and 8.5 is effective (recommended: 7).
|
||||
3. **Minimum Steps:** Ensure at least 36 steps for optimal results.
|
||||
4. **Compatibility:**
|
||||
- Works excellently with Hires fix.
|
||||
- Great results with unaestheticXLv31 embedding, although sometimes better without it.
|
||||
5. **Sampling Method:** Either DPM++ 2M SDE Karras or DPM++ 2M Karras.
|
||||
|
||||
Have fun! 😊
|
||||
"#;
|
||||
let sdxl_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/be706282-2978-42a0-aaa2-73881aad94e9/width=1024/00049-2287632957-1girl,face,curly%20hair,red%20hair,white%20background,.jpeg";
|
||||
|
||||
model_weight_token = ModelWeightToken(format!("{}", i));
|
||||
title = format!("SDXL_Niji_Special Edition: {}", i);
|
||||
description = format!("Description Number {}:", i);
|
||||
|
||||
// private_bucket_hash = format!("bucket_hash{}", i);
|
||||
private_bucket_prefix = format!("_fake");
|
||||
private_bucket_extension = format!("sdxl");
|
||||
|
||||
description_rendered_html = sdxl_markdown_description;
|
||||
|
||||
original_filename = format!("filename{}.txt", i);
|
||||
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
|
||||
|
||||
// NOTE TO SELF this will be a join for later on for now they can use this.
|
||||
thumbnail_token = "";
|
||||
|
||||
cached_user_ratings_total_count = i;
|
||||
cached_user_ratings_positive_count = i;
|
||||
cached_user_ratings_negative_count = i;
|
||||
cached_user_ratings_ratio = i as u32 / 100;
|
||||
version = i as i32;
|
||||
println!("Seeding SDXL model {}", i);
|
||||
},
|
||||
81..=100 => {
|
||||
// LoRA
|
||||
let lora_markdown_description = r#"
|
||||
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
|
||||
- LoRA Gawr Gura
|
||||
"#;
|
||||
|
||||
let lora_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/123645df-dee2-4239-863a-76a150b09c32/width=1024/00000-2171948503.jpeg";
|
||||
model_weight_token = ModelWeightToken(format!("{}", i));
|
||||
title = format!("Gawr Gura LoRA: {}", i);
|
||||
|
||||
description = format!("Description Number {}:", i);
|
||||
|
||||
description_rendered_html = lora_markdown_description;
|
||||
|
||||
original_filename = format!("filename{}.txt", i);
|
||||
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
|
||||
|
||||
// NOTE TO SELF this will be a join for later on for now they can use this.
|
||||
thumbnail_token = ""; // lora_image_token;
|
||||
|
||||
// private_bucket_hash = format!("bucket_hash{}", i);
|
||||
private_bucket_prefix = format!("_fake");
|
||||
private_bucket_extension = format!("loRA");
|
||||
|
||||
cached_user_ratings_total_count = i;
|
||||
cached_user_ratings_positive_count = i;
|
||||
cached_user_ratings_negative_count = i;
|
||||
cached_user_ratings_ratio = i as u32 / 100;
|
||||
version = i as i32;
|
||||
println!("Seeding LoRA model {}", i);
|
||||
},
|
||||
_ => {
|
||||
// We went out of range so have more gwar gura
|
||||
let rvc_markdown_description = r#"
|
||||
# RVC v2
|
||||
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
|
||||
- Gawr Gura MOS 4
|
||||
"#;
|
||||
|
||||
model_weight_token = ModelWeightToken(format!("{}", i));
|
||||
title = format!("Gawr Gura: {}", i);
|
||||
description = format!("Description Number {} The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.", i);
|
||||
description_rendered_html = rvc_markdown_description;
|
||||
|
||||
original_filename = format!("gura_gwar{}.safetensors", i);
|
||||
original_download_url = format!("www.google.ca");
|
||||
|
||||
// NOTE TO SELF this will be a join for later on for now they can use this.
|
||||
thumbnail_token = "";
|
||||
|
||||
// private_bucket_hash = format!("bucket_hash{}", i);
|
||||
private_bucket_prefix = format!("_fake");
|
||||
private_bucket_extension = format!("rvcV2");
|
||||
cached_user_ratings_total_count = i;
|
||||
cached_user_ratings_positive_count = i;
|
||||
cached_user_ratings_negative_count = i;
|
||||
cached_user_ratings_ratio = i as u32 / 100;
|
||||
version = i as i32;
|
||||
|
||||
println!("Out of range");
|
||||
}
|
||||
}
|
||||
|
||||
let args = CreateModelWeightsArgs {
|
||||
token: &model_weight_token, // replace with actual ModelWeightToken
|
||||
weights_type: weights_types, // replace with actual WeightsType
|
||||
weights_category: weights_category, // replace with actual WeightsCategory
|
||||
title: title,
|
||||
maybe_thumbnail_token: Some(thumbnail_token.to_string()),
|
||||
description_markdown: description,
|
||||
description_rendered_html: description_rendered_html.to_string(),
|
||||
creator_user_token: Some(&user_token), // replace with actual UserToken
|
||||
creator_ip_address: "192.168.1.1",
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_last_update_user_token: Some(user_token.to_string()),
|
||||
original_download_url: Some(original_download_url),
|
||||
original_filename: Some(original_filename),
|
||||
file_size_bytes: 1024,
|
||||
file_checksum_sha2: "checksum1".to_string(),
|
||||
private_bucket_hash: "bucket_hash1".to_string(),
|
||||
maybe_private_bucket_prefix: Some(private_bucket_prefix),
|
||||
maybe_private_bucket_extension: Some(private_bucket_extension),
|
||||
cached_user_ratings_total_count: cached_user_ratings_total_count,
|
||||
cached_user_ratings_positive_count: cached_user_ratings_positive_count,
|
||||
cached_user_ratings_negative_count: cached_user_ratings_negative_count,
|
||||
maybe_cached_user_ratings_ratio: Some(cached_user_ratings_ratio as f32),
|
||||
version: version,
|
||||
mysql_pool: &mysql_pool, // replace with actual MySqlPool
|
||||
};
|
||||
|
||||
create_weight(args).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn seed_weights_for_user_token(
|
||||
mysql_pool: &Pool<MySql>,
|
||||
user_token: UserToken
|
||||
@@ -165,16 +504,9 @@ pub async fn seed_weights_for_user_token(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
|
||||
info!("Seeding weights...");
|
||||
|
||||
let user_token = match get_user_token_by_username(HANASHI_USERNAME, mysql_pool).await? {
|
||||
None => {
|
||||
return Err(anyhow!("could not find user hanashi"));
|
||||
}
|
||||
Some(token) => token,
|
||||
};
|
||||
|
||||
pub async fn original_seed_weights(mysql_pool: &Pool<MySql>,user_token: UserToken) -> AnyhowResult<()> {
|
||||
let model_weight_token1 = ModelWeightToken("1".to_string());
|
||||
let creator_token1 = UserToken("creatorToken1".to_string());
|
||||
|
||||
@@ -327,9 +659,22 @@ pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
|
||||
create_weight(model_weights_arg).await?;
|
||||
}
|
||||
|
||||
// adds this to HANASHI
|
||||
get_user_token_by_username(HANASHI_USERNAME, mysql_pool).await?;
|
||||
seed_weights_for_user_token(mysql_pool, user_token).await?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
|
||||
info!("Seeding weights...");
|
||||
|
||||
let user_token = match get_user_token_by_username(HANASHI_USERNAME, mysql_pool).await? {
|
||||
None => {
|
||||
return Err(anyhow!("could not find user hanashi"));
|
||||
}
|
||||
Some(token) => token,
|
||||
};
|
||||
|
||||
//original_seed_weights(mysql_pool,user_token).await?;
|
||||
//seed_weights_for_user_token(mysql_pool, user_token).await?;
|
||||
seed_weights_for_paging(mysql_pool,user_token).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::warn;
|
||||
use sqlx::{MySql, MySqlPool};
|
||||
|
||||
@@ -7,6 +7,7 @@ use enums::by_table::model_weights::{
|
||||
use log::warn;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::{ users::UserToken, model_weights::ModelWeightToken };
|
||||
|
||||
pub struct CreateModelWeightsArgs<'a> {
|
||||
pub token: &'a ModelWeightToken,
|
||||
pub weights_type: WeightsType,
|
||||
|
||||
@@ -71,11 +71,7 @@ pub async fn list_weights_by_creator_username(
|
||||
let raw_weights: Vec<RawWeightJoinUser> = get_raw_weights_by_creator_username(&mut connection, creator_username, can_see_deleted).await?;
|
||||
let weights_records: Vec<WeightsJoinUserRecord> = map_to_weights(raw_weights).await;
|
||||
|
||||
let filtered_weights: Vec<WeightsJoinUserRecord> = weights_records.into_iter().filter(|weight| {
|
||||
weight.creator_username == creator_username && weight.creator_set_visibility == Visibility::Public && can_see_deleted
|
||||
}).collect();
|
||||
|
||||
Ok(filtered_weights)
|
||||
Ok(weights_records)
|
||||
}
|
||||
|
||||
async fn get_raw_weights_by_creator_username(
|
||||
|
||||
@@ -133,13 +133,13 @@ impl ListWeightsQueryBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn weights_type(mut self, weights_type: WeightsType) -> Self {
|
||||
self.weights_type = Some(weights_type);
|
||||
pub fn weights_type(mut self, weights_type: Option<WeightsType>) -> Self {
|
||||
self.weights_type = weights_type;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn weights_category(mut self, weights_category: WeightsCategory) -> Self {
|
||||
self.weights_category = Some(weights_category);
|
||||
pub fn weights_category(mut self, weights_category: Option<WeightsCategory>) -> Self {
|
||||
self.weights_category = weights_category;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -505,7 +505,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn predicates_scoped_to_weights_type() {
|
||||
let query_builder = ListWeightsQueryBuilder::new().weights_type(WeightsType::RvcV2);
|
||||
let query_builder = ListWeightsQueryBuilder::new().weights_type(Some(WeightsType::RvcV2));
|
||||
assert_eq!(
|
||||
&query_builder.build_predicates(),
|
||||
" WHERE model_weights.weights_type = ? \
|
||||
@@ -520,7 +520,7 @@ mod tests {
|
||||
#[test]
|
||||
fn predicates_scoped_to_weights_category() {
|
||||
let query_builder = ListWeightsQueryBuilder::new().weights_category(
|
||||
WeightsCategory::VoiceConversion
|
||||
Some(WeightsCategory::VoiceConversion)
|
||||
);
|
||||
println!("Query HERE! {}", &query_builder.build_predicates());
|
||||
assert_eq!(
|
||||
|
||||
@@ -3,8 +3,9 @@ mod tests {
|
||||
use std::result;
|
||||
|
||||
use anyhow::Ok;
|
||||
use enums::by_table::model_weights::{weights_types, weights_category};
|
||||
use sqlx::{MySqlPool, Acquire};
|
||||
use chrono::offset;
|
||||
use enums::by_table::model_weights::{ weights_types, weights_category };
|
||||
use sqlx::{ MySqlPool, Acquire };
|
||||
use rand::Rng;
|
||||
|
||||
use tokio;
|
||||
@@ -24,7 +25,12 @@ mod tests {
|
||||
use crate::queries::model_weights::create_weight::create_weight;
|
||||
use crate::queries::model_weights::get_weight::get_weight_by_token;
|
||||
|
||||
use crate::queries::model_weights::delete_weights::{ delete_weights_as_user, delete_weights_as_mod, undelete_weights_as_mod , undelete_weights_as_user};
|
||||
use crate::queries::model_weights::delete_weights::{
|
||||
delete_weights_as_user,
|
||||
delete_weights_as_mod,
|
||||
undelete_weights_as_mod,
|
||||
undelete_weights_as_user,
|
||||
};
|
||||
use crate::queries::model_weights::list_weights_by_user::list_weights_by_creator_username;
|
||||
|
||||
use crate::queries::model_weights::list_weights_query_builder::ListWeightsQueryBuilder;
|
||||
@@ -45,23 +51,19 @@ mod tests {
|
||||
|
||||
pub async fn delete_all_weights_for_table(mysql_pool: &MySqlPool) -> AnyhowResult<()> {
|
||||
// write a query that deletes all weights
|
||||
let _r = sqlx::query("DELETE FROM model_weights")
|
||||
.execute(mysql_pool)
|
||||
.await?;
|
||||
let _r = sqlx::query("DELETE FROM model_weights").execute(mysql_pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_create_weights() -> AnyhowResult<()> {
|
||||
|
||||
let pool = setup().await;
|
||||
// create a random token for the model weight
|
||||
let mut rng = rand::thread_rng();
|
||||
let random_number: u32 = rng.gen();
|
||||
let model_weight_token1 = ModelWeightToken(random_number.to_string());
|
||||
|
||||
|
||||
let creator_token1 = UserToken("creatorToken!1".to_string());
|
||||
|
||||
let args = CreateModelWeightsArgs {
|
||||
@@ -131,7 +133,6 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_delete_and_undelete_weights_user() -> AnyhowResult<()> {
|
||||
|
||||
let pool = setup().await;
|
||||
// create a random token for the model weight
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -170,7 +171,7 @@ mod tests {
|
||||
create_weight(args).await?;
|
||||
|
||||
delete_weights_as_user(&model_weight_token1, &pool).await?;
|
||||
|
||||
|
||||
let result = get_weight_by_token(&model_weight_token1, true, &pool).await?;
|
||||
let result = result.unwrap();
|
||||
|
||||
@@ -261,7 +262,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
undelete_weights_as_mod(&model_weight_token1, &pool).await?;
|
||||
let result = get_weight_by_token(&model_weight_token1, true, &pool).await?;
|
||||
let result = result.unwrap();
|
||||
@@ -285,35 +285,54 @@ mod tests {
|
||||
#[serial]
|
||||
async fn list_all_weights_by_user() -> AnyhowResult<()> {
|
||||
let pool = setup().await;
|
||||
|
||||
|
||||
let creator_username = "hanashi".to_string();
|
||||
let can_see_deleted = true;
|
||||
|
||||
seed_weights_with_category_and_type(WeightsType::LoRA,WeightsCategory::ImageGeneration,5,&creator_username).await?;
|
||||
seed_weights_with_category_and_type(
|
||||
WeightsType::LoRA,
|
||||
WeightsCategory::ImageGeneration,
|
||||
5,
|
||||
&creator_username
|
||||
).await?;
|
||||
|
||||
let weights_by_username = list_weights_by_creator_username(&pool, &creator_username, can_see_deleted).await?;
|
||||
let weights_by_username = list_weights_by_creator_username(
|
||||
&pool,
|
||||
&creator_username,
|
||||
can_see_deleted
|
||||
).await?;
|
||||
for weight in weights_by_username.iter() {
|
||||
// print weight
|
||||
println!("weight: {:?}", weight.creator_username);
|
||||
}
|
||||
assert_eq!(weights_by_username.len(), 5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn seed_weights_with_category_and_type(weights_type:WeightsType,weights_category:WeightsCategory,number_of_items:u32,creator_username:&str) -> AnyhowResult<()> {
|
||||
|
||||
async fn seed_weights_with_category_and_type(
|
||||
weights_type: WeightsType,
|
||||
weights_category: WeightsCategory,
|
||||
number_of_items: u32,
|
||||
creator_username: &str
|
||||
) -> AnyhowResult<()> {
|
||||
let pool = setup().await;
|
||||
let creator_token = get_user_token_by_username(&creator_username, &pool).await?.unwrap_or_else(
|
||||
|| panic!("Could not find user with username {}", creator_username)
|
||||
let creator_token = get_user_token_by_username(
|
||||
&creator_username,
|
||||
&pool
|
||||
).await?.unwrap_or_else(||
|
||||
panic!("Could not find user with username {}", creator_username)
|
||||
);
|
||||
|
||||
for i in 0..number_of_items {
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let number: u32 = rng.gen();
|
||||
|
||||
let model_weight_token = ModelWeightToken(number.to_string());
|
||||
|
||||
|
||||
let args = CreateModelWeightsArgs {
|
||||
token: &model_weight_token, // replace with actual ModelWeightToken
|
||||
weights_type:weights_type, // replace with actual WeightsType
|
||||
weights_type: weights_type, // replace with actual WeightsType
|
||||
weights_category: weights_category, // replace with actual WeightsCategory
|
||||
title: format!("Title {}", i),
|
||||
maybe_thumbnail_token: Some(format!("Thumbnail {}", i)),
|
||||
@@ -342,7 +361,6 @@ mod tests {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
// Tests paging for the list weights query builder
|
||||
@@ -352,40 +370,45 @@ mod tests {
|
||||
let pool = setup().await;
|
||||
let creator_username = "hanashi".to_string();
|
||||
|
||||
seed_weights_with_category_and_type(WeightsType::LoRA,WeightsCategory::ImageGeneration,20,&creator_username).await?;
|
||||
|
||||
seed_weights_with_category_and_type(
|
||||
WeightsType::LoRA,
|
||||
WeightsCategory::ImageGeneration,
|
||||
20,
|
||||
&creator_username
|
||||
).await?;
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.offset(Some(0))
|
||||
.weights_type(WeightsType::LoRA)
|
||||
.weights_category(WeightsCategory::ImageGeneration)
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false);
|
||||
|
||||
.offset(Some(0))
|
||||
.weights_type(Some(WeightsType::LoRA))
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
|
||||
// let result = qb.perform_query_for_page(&pool).await?;
|
||||
assert_eq!(result.weights.len(), 10);
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.weights_type(WeightsType::LoRA)
|
||||
.weights_category(WeightsCategory::ImageGeneration)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.offset(Some(10))
|
||||
.limit(10);
|
||||
.weights_type(Some(WeightsType::LoRA))
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.offset(Some(10))
|
||||
.limit(10);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
assert_eq!(result.weights.len(), 10);
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.weights_type(WeightsType::LoRA)
|
||||
.weights_category(WeightsCategory::ImageGeneration)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.offset(Some(10))
|
||||
.limit(10);
|
||||
.weights_type(Some(WeightsType::LoRA))
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.offset(Some(10))
|
||||
.limit(10);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
@@ -394,27 +417,31 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn list_weights_query_build_test_asc_desc_cursor_reverse() -> AnyhowResult<()> {
|
||||
let pool = setup().await;
|
||||
let creator_username = "hanashi".to_string();
|
||||
|
||||
seed_weights_with_category_and_type(WeightsType::LoRA,WeightsCategory::ImageGeneration,10,&creator_username).await?;
|
||||
|
||||
seed_weights_with_category_and_type(
|
||||
WeightsType::LoRA,
|
||||
WeightsCategory::ImageGeneration,
|
||||
10,
|
||||
&creator_username
|
||||
).await?;
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.offset(Some(0))
|
||||
.weights_type(WeightsType::LoRA)
|
||||
.weights_category(WeightsCategory::ImageGeneration)
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(true)
|
||||
.cursor_is_reversed(false);
|
||||
.offset(Some(0))
|
||||
.weights_type(Some(WeightsType::LoRA))
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(true)
|
||||
.cursor_is_reversed(false);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
|
||||
// write code that will loop through the result and check if the ids are in ascending order
|
||||
let mut previous_id = 0;
|
||||
for weight in result.weights.iter() {
|
||||
@@ -424,19 +451,18 @@ mod tests {
|
||||
}
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.offset(Some(0))
|
||||
.weights_type(WeightsType::LoRA)
|
||||
.weights_category(WeightsCategory::ImageGeneration)
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(false)
|
||||
.cursor_is_reversed(true);
|
||||
.offset(Some(0))
|
||||
.weights_type(Some(WeightsType::LoRA))
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(false)
|
||||
.cursor_is_reversed(true);
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
// write code that will loop through the result and check if the ids are in descending order
|
||||
let mut previous_id
|
||||
= 10000;
|
||||
let mut previous_id = 10000;
|
||||
for weight in result.weights.iter() {
|
||||
let current_id = weight.weight_id;
|
||||
assert!(current_id < previous_id);
|
||||
@@ -452,18 +478,23 @@ mod tests {
|
||||
let pool = setup().await;
|
||||
let creator_username = "hanashi".to_string();
|
||||
|
||||
seed_weights_with_category_and_type(WeightsType::LoRA,WeightsCategory::ImageGeneration,10,&creator_username).await?;
|
||||
|
||||
seed_weights_with_category_and_type(
|
||||
WeightsType::LoRA,
|
||||
WeightsCategory::ImageGeneration,
|
||||
10,
|
||||
&creator_username
|
||||
).await?;
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.offset(Some(0))
|
||||
.weights_type(WeightsType::LoRA)
|
||||
.weights_category(WeightsCategory::ImageGeneration)
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(true);
|
||||
.offset(Some(0))
|
||||
.weights_type(Some(WeightsType::LoRA))
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(true);
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
|
||||
// write code that will loop through the result and check if the ids are in ascending order
|
||||
let mut previous_id = 0;
|
||||
for weight in result.weights.iter() {
|
||||
@@ -473,19 +504,18 @@ mod tests {
|
||||
}
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.offset(Some(0))
|
||||
.weights_type(WeightsType::LoRA)
|
||||
.weights_category(WeightsCategory::ImageGeneration)
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(false);
|
||||
.offset(Some(0))
|
||||
.weights_type(Some(WeightsType::LoRA))
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(10)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(false);
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
// write code that will loop through the result and check if the ids are in descending order
|
||||
|
||||
let mut previous_id
|
||||
= 10000;
|
||||
let mut previous_id = 10000;
|
||||
for weight in result.weights.iter() {
|
||||
let current_id = weight.weight_id;
|
||||
assert!(current_id < previous_id);
|
||||
@@ -499,11 +529,14 @@ mod tests {
|
||||
#[serial]
|
||||
async fn list_weights_query_build_test() -> AnyhowResult<()> {
|
||||
let pool = setup().await;
|
||||
|
||||
|
||||
let creator_username = "hanashi".to_string();
|
||||
|
||||
let creator_token = get_user_token_by_username(&creator_username, &pool).await?.unwrap_or_else(
|
||||
|| panic!("Could not find user with username {}", creator_username)
|
||||
let creator_token = get_user_token_by_username(
|
||||
&creator_username,
|
||||
&pool
|
||||
).await?.unwrap_or_else(||
|
||||
panic!("Could not find user with username {}", creator_username)
|
||||
);
|
||||
|
||||
for i in 0..6 {
|
||||
@@ -515,7 +548,7 @@ mod tests {
|
||||
WeightsType::RvcV2
|
||||
} else if i % 5 == 3 {
|
||||
WeightsType::StableDiffusionXL
|
||||
} else if i ^ 5 == 4 {
|
||||
} else if (i ^ 5) == 4 {
|
||||
WeightsType::SoVitsSvc
|
||||
} else {
|
||||
WeightsType::StableDiffusion15
|
||||
@@ -529,7 +562,7 @@ mod tests {
|
||||
WeightsCategory::VoiceConversion
|
||||
} else if i % 5 == 3 {
|
||||
WeightsCategory::ImageGeneration
|
||||
} else if i ^ 5 == 4 {
|
||||
} else if (i ^ 5) == 4 {
|
||||
WeightsCategory::VoiceConversion
|
||||
} else {
|
||||
WeightsCategory::ImageGeneration
|
||||
@@ -539,10 +572,10 @@ mod tests {
|
||||
let number: u32 = rng.gen();
|
||||
|
||||
let model_weight_token = ModelWeightToken(number.to_string());
|
||||
|
||||
|
||||
let args = CreateModelWeightsArgs {
|
||||
token: &model_weight_token, // replace with actual ModelWeightToken
|
||||
weights_type:weights_type, // replace with actual WeightsType
|
||||
weights_type: weights_type, // replace with actual WeightsType
|
||||
weights_category: weights_category, // replace with actual WeightsCategory
|
||||
title: format!("Title {}", i),
|
||||
maybe_thumbnail_token: Some(format!("Thumbnail {}", i)),
|
||||
@@ -572,28 +605,192 @@ mod tests {
|
||||
|
||||
// try a query with all the filters
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.weights_type(WeightsType::Tacotron2)
|
||||
.weights_category(WeightsCategory::TextToSpeech)
|
||||
.weights_type(Some(WeightsType::Tacotron2))
|
||||
.weights_category(Some(WeightsCategory::TextToSpeech))
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false);
|
||||
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
assert_eq!(result.weights.len(), 2);
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.weights_type(WeightsType::LoRA)
|
||||
.weights_category(WeightsCategory::ImageGeneration)
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false);
|
||||
.weights_type(Some(WeightsType::LoRA))
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
assert_eq!(result.weights.len(), 1);
|
||||
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
|
||||
assert_eq!(result.weights[0].weights_type, WeightsType::LoRA);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn list_weights_query_builder_page_type_and_category() -> AnyhowResult<()> {
|
||||
let db_connection_string = DEFAULT_MYSQL_CONNECTION_STRING;
|
||||
let pool = MySqlPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&db_connection_string).await
|
||||
.unwrap();
|
||||
// using the seeded weights from the previous test
|
||||
let creator_username = "hanashi".to_string();
|
||||
|
||||
for i in 0..4 {
|
||||
let weights_types = vec![
|
||||
WeightsType::HifiganTacotron2,
|
||||
WeightsType::LoRA,
|
||||
WeightsType::RvcV2,
|
||||
WeightsType::StableDiffusionXL
|
||||
];
|
||||
|
||||
let weights_type = weights_types[i as usize];
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.weights_type(Some(weights_type))
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false);
|
||||
|
||||
println!("weights_type: {:?}", weights_type);
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
assert_eq!(result.weights.len(), 20, "Should be 20 weights");
|
||||
assert_eq!(
|
||||
result.weights[0].weights_type,
|
||||
weights_type,
|
||||
"Should be the same weights type"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn list_weight_query_builder_all() -> AnyhowResult<()> {
|
||||
let db_connection_string = DEFAULT_MYSQL_CONNECTION_STRING;
|
||||
let pool = MySqlPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&db_connection_string).await
|
||||
.unwrap();
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.limit(11);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
assert_eq!(result.weights.len(), 11);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn list_limits() -> AnyhowResult<()> {
|
||||
let db_connection_string = DEFAULT_MYSQL_CONNECTION_STRING;
|
||||
|
||||
let pool = MySqlPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&db_connection_string).await
|
||||
.unwrap();
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(10);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
assert_eq!(result.weights.len(), 10);
|
||||
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
// by default 25
|
||||
assert_eq!(result.weights.len(), 25);
|
||||
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
|
||||
// over query limit
|
||||
let qb: ListWeightsQueryBuilder = ListWeightsQueryBuilder::new()
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(80);
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
assert_eq!(result.weights.len(), 60);
|
||||
// pick a random value in here
|
||||
assert_eq!(result.weights[9].weights_category, WeightsCategory::ImageGeneration);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn page_across_a_category() -> AnyhowResult<()> {
|
||||
// paging across a category and it works
|
||||
let db_connection_string = DEFAULT_MYSQL_CONNECTION_STRING;
|
||||
// how we implement paging.
|
||||
let page:u16 = 0;
|
||||
let page_size: u16 = 20;
|
||||
|
||||
let pool = MySqlPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&db_connection_string).await
|
||||
.unwrap();
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.sort_ascending(true)
|
||||
.limit(page_size);
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
assert_eq!(result.weights.len(), page_size as usize);
|
||||
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
|
||||
|
||||
let page = 1;
|
||||
let offset = page * page_size;
|
||||
|
||||
assert_eq!(result.weights.len(), page_size as usize);
|
||||
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
|
||||
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(page_size)
|
||||
.sort_ascending(true)
|
||||
.offset(Some(offset.into()));
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
let page = 2;
|
||||
let offset = page * page_size;
|
||||
|
||||
assert_eq!(result.weights.len(), page_size as usize);
|
||||
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
|
||||
|
||||
let qb = ListWeightsQueryBuilder::new()
|
||||
.scope_creator_username(Some("hanashi"))
|
||||
.include_user_deleted_results(false)
|
||||
.sort_ascending(true)
|
||||
.weights_category(Some(WeightsCategory::ImageGeneration))
|
||||
.limit(page_size)
|
||||
.offset(Some(offset.into()));
|
||||
|
||||
let result = qb.perform_query_for_page(&pool).await?;
|
||||
|
||||
assert_eq!(result.weights.len(), page_size as usize);
|
||||
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,37 +9,38 @@ pub struct UpdateWeightArgs<'a> {
|
||||
pub weight_token: &'a ModelWeightToken,
|
||||
pub title: Option<&'a str>,
|
||||
pub maybe_thumbnail_token: Option<&'a str>,
|
||||
pub description_markdown: &'a str,
|
||||
pub description_rendered_html: &'a str,
|
||||
pub creator_set_visibility: &'a Visibility,
|
||||
pub description_markdown: Option<&'a str>,
|
||||
pub description_rendered_html: Option<&'a str>,
|
||||
pub creator_set_visibility: Option<&'a Visibility>,
|
||||
pub weights_type: Option<String>,
|
||||
pub weights_category: Option<String>,
|
||||
pub mysql_pool: &'a MySqlPool,
|
||||
}
|
||||
|
||||
pub async fn update_weights(args: UpdateWeightArgs<'_>) -> AnyhowResult<()> {
|
||||
let mut transaction = args.mysql_pool.begin().await?;
|
||||
let transaction = args.mysql_pool.begin().await?;
|
||||
|
||||
let query_result = sqlx
|
||||
::query!(
|
||||
r#"
|
||||
UPDATE model_weights
|
||||
SET
|
||||
title = ?,
|
||||
description_markdown = ?,
|
||||
maybe_thumbnail_token = ?,
|
||||
description_rendered_html = ?,
|
||||
creator_set_visibility = ?,
|
||||
version = version + 1
|
||||
WHERE token = ?
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.title,
|
||||
args.description_markdown,
|
||||
args.maybe_thumbnail_token,
|
||||
args.description_rendered_html,
|
||||
args.creator_set_visibility.to_str(),
|
||||
args.weight_token.as_str()
|
||||
)
|
||||
.execute(args.mysql_pool).await;
|
||||
|
||||
let query_result = sqlx::query!(
|
||||
r#"
|
||||
UPDATE model_weights
|
||||
SET
|
||||
title = COALESCE(?, title),
|
||||
description_markdown = COALESCE(?, description_markdown),
|
||||
maybe_thumbnail_token = COALESCE(?, maybe_thumbnail_token),
|
||||
description_rendered_html = COALESCE(?, description_rendered_html),
|
||||
creator_set_visibility = COALESCE(?, creator_set_visibility),
|
||||
version = version + 1
|
||||
WHERE token = ?
|
||||
"#,
|
||||
args.title.as_deref(),
|
||||
args.description_markdown.as_deref(),
|
||||
args.maybe_thumbnail_token.as_deref(),
|
||||
args.description_rendered_html.as_deref(),
|
||||
args.creator_set_visibility.as_deref(),
|
||||
args.weight_token.as_str()
|
||||
)
|
||||
.execute(args.mysql_pool).await;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
@@ -53,6 +54,7 @@ pub async fn update_weights(args: UpdateWeightArgs<'_>) -> AnyhowResult<()> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
// Template
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use tokio;
|
||||
|
||||
@@ -162,7 +162,9 @@ pulldown-cmark = "0.9.3"
|
||||
# The initial round of stabilizations for the async/await language feature in Rust 1.39 did not
|
||||
# include support for async fn in traits.
|
||||
async-trait = "0.1.58"
|
||||
utoipa = { version = "4", features = ["actix_extras"] }
|
||||
|
||||
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
|
||||
utoipa = { version = "4", features = ["chrono","actix_extras"] }
|
||||
utoipa-swagger-ui = { version = "5", features = ["actix-web"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -1,11 +1,49 @@
|
||||
|
||||
use crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::EnqueueTTSRequest;
|
||||
use crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::EnqueueTTSRequestSuccessResponse;
|
||||
use crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::EnqueueTTSRequestError;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::*;
|
||||
|
||||
use crate::http_server::endpoints::weights::list_available_weights::*;
|
||||
use crate::http_server::web_utils::response_success_helpers::*;
|
||||
use crate::http_server::endpoints::weights::delete_weight::*;
|
||||
use crate::http_server::endpoints::weights::get_weight::*;
|
||||
use crate::http_server::endpoints::weights::update_weight::*;
|
||||
use crate::http_server::endpoints::weights::list_weights_by_user::*;
|
||||
use crate::http_server::common_responses::user_details_lite::{DefaultAvatarInfo, UserDetailsLight};
|
||||
use enums::by_table::model_weights::{
|
||||
weights_types::WeightsType,
|
||||
weights_category::WeightsCategory,
|
||||
};
|
||||
|
||||
use tokens::tokens::generic_inference_jobs::*;
|
||||
use tokens::tokens::model_weights::*;
|
||||
use tokens::tokens::users::*;
|
||||
use enums::common::visibility::Visibility;
|
||||
use chrono::{DateTime, Utc};
|
||||
use utoipa::OpenApi;
|
||||
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(paths(crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::enqueue_tts_request),
|
||||
components(schemas(EnqueueTTSRequest,EnqueueTTSRequestSuccessResponse,EnqueueTTSRequestError,InferenceJobToken)))]
|
||||
#[openapi(paths(crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::enqueue_tts_request,
|
||||
crate::http_server::endpoints::weights::list_available_weights::list_available_weights_handler,
|
||||
crate::http_server::endpoints::weights::delete_weight::delete_weight_handler,
|
||||
crate::http_server::endpoints::weights::update_weight::update_weight_handler,
|
||||
crate::http_server::endpoints::weights::get_weight::get_weight_handler,
|
||||
crate::http_server::endpoints::weights::list_weights_by_user::list_weights_by_user_handler
|
||||
),components(schemas(
|
||||
// Tokens
|
||||
UserToken,Visibility,
|
||||
//
|
||||
SimpleGenericJsonSuccess,
|
||||
// Inference
|
||||
EnqueueTTSRequest,EnqueueTTSRequestSuccessResponse,EnqueueTTSRequestError,InferenceJobToken,
|
||||
//Model Weights
|
||||
ModelWeightToken,WeightsCategory,WeightsType,
|
||||
GetWeightPathInfo,GetWeightResponse,GetWeightError,
|
||||
UpdateWeightRequest,UpdateWeightRequest,UpdateWeightPathInfo,UpdateWeightRequest,UpdateWeightError,
|
||||
DeleteWeightPathInfo,DeleteWeightRequest,DeleteWeightError,DeleteWeightRequest,DeleteWeightError,
|
||||
ListWeightError,ModelWeightForList,ListWeightError,
|
||||
ListAvailableWeightsQuery,ListAvailableWeightsSuccessResponse,ModelWeightForList,
|
||||
ListWeightsByUserError,ListWeightsByUserSuccessResponse,ListWeightsByUserPathInfo,Weight,
|
||||
UserDetailsLight,DefaultAvatarInfo
|
||||
)))]
|
||||
pub struct ApiDoc;
|
||||
|
||||
|
||||
+4
-3
@@ -2,9 +2,9 @@ use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::user_avatars::default_avatar_color_from_username::default_avatar_color_from_username;
|
||||
use crate::user_avatars::default_avatar_from_username::default_avatar_from_username;
|
||||
|
||||
use utoipa::ToSchema;
|
||||
/// Everything we need to refer to a user on the public web interface.
|
||||
#[derive(Clone, Serialize)]
|
||||
#[derive(Clone, Serialize,ToSchema)]
|
||||
pub struct UserDetailsLight {
|
||||
/// The token for the user
|
||||
pub user_token: UserToken,
|
||||
@@ -82,7 +82,8 @@ impl UserDetailsLight {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
|
||||
#[derive(Clone, Serialize,ToSchema)]
|
||||
pub struct DefaultAvatarInfo {
|
||||
pub image_index: u8,
|
||||
pub color_index: u8,
|
||||
|
||||
@@ -25,4 +25,5 @@ pub mod voice_clone_requests;
|
||||
pub mod voice_conversion;
|
||||
pub mod voice_designer;
|
||||
pub mod w2l;
|
||||
pub mod favorites;
|
||||
pub mod favorites;
|
||||
pub mod weights;
|
||||
+3
-6
@@ -43,23 +43,20 @@ const DEBUG_HEADER_NAME: &str = "enable-debug-mode";
|
||||
const ROUTING_TAG_HEADER_NAME: &str = "routing-tag";
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(ToSchema)]
|
||||
#[derive(Deserialize,ToSchema)]
|
||||
pub struct EnqueueTTSRequest {
|
||||
uuid_idempotency_token: String,
|
||||
text: String,
|
||||
voice_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(ToSchema)]
|
||||
#[derive(Serialize,ToSchema)]
|
||||
pub struct EnqueueTTSRequestSuccessResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(ToSchema)]
|
||||
#[derive(Debug,ToSchema)]
|
||||
pub enum EnqueueTTSRequestError {
|
||||
BadInput(String),
|
||||
NotAuthorized,
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use enums::by_table::model_weights::weights_category::WeightsCategory;
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use mysql_queries::queries::model_weights::create_weight::{self, create_weight};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
use crate::http_server::endpoints::media_uploads::common::upload_error::UploadError;
|
||||
use crate::server_state::ServerState;
|
||||
|
||||
use log::{error, info, warn};
|
||||
|
||||
use buckets::public::weight_uploads::original_file::WeightUploadOriginalFilePath;
|
||||
use enums::by_table::media_uploads::media_upload_source::MediaUploadSource;
|
||||
use enums::by_table::media_uploads::media_upload_type::MediaUploadType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use media::decode_basic_audio_info::decode_basic_audio_bytes_info;
|
||||
use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes;
|
||||
use mysql_queries::queries::media_uploads::get_media_upload_by_uuid::get_media_upload_by_uuid_with_connection;
|
||||
use mysql_queries::queries::media_uploads::insert_media_upload::{Args, insert_media_upload};
|
||||
|
||||
use crate::http_server::endpoints::media_uploads::common::drain_multipart_request::{drain_multipart_request, MediaSource};
|
||||
use crate::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Serialize,ToSchema)]
|
||||
pub struct UploadWeightsSuccessResponse {
|
||||
pub success: bool,
|
||||
pub upload_token: ModelWeightToken,
|
||||
}
|
||||
|
||||
static ALLOWED_MIME_TYPES : Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
||||
HashSet::from([
|
||||
"binary/octet-stream",
|
||||
])
|
||||
});
|
||||
|
||||
// pub async fn create_weight_handler(
|
||||
// http_request: HttpRequest,
|
||||
// server_state: web::Data<Arc<ServerState>>,
|
||||
// mut multipart_payload: Multipart,
|
||||
// ) -> Result<HttpResponse, UploadError> {
|
||||
|
||||
|
||||
// let weight_upload_token = response.to_model_token();
|
||||
|
||||
// let response: UploadWeightsSuccessResponse = UploadWeightsSuccessResponse {
|
||||
// success: true,
|
||||
// upload_token: weight_upload_token,
|
||||
// };
|
||||
|
||||
// let body = serde_json::to_string(&response)
|
||||
// .map_err(|e| UploadError::ServerError)?;
|
||||
|
||||
// return Ok(HttpResponse::Ok()
|
||||
// .content_type("application/json")
|
||||
// .body(body));
|
||||
// }
|
||||
|
||||
// pub enum SuccessCase {
|
||||
// WeightAlreadyUploaded {
|
||||
// existing_upload_token: ModelWeightToken,
|
||||
// },
|
||||
// WeightSuccessfullyUploaded {
|
||||
// upload_token: ModelWeightToken,
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl SuccessCase {
|
||||
// pub fn to_model_token(self) -> ModelWeightToken {
|
||||
// match self {
|
||||
// SuccessCase::WeightAlreadyUploaded { existing_upload_token } => existing_upload_token,
|
||||
// SuccessCase::WeightSuccessfullyUploaded { upload_token } => upload_token,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// let token = create_weight(CreateModelWeightsArgs {
|
||||
// token: &ModelWeightToken::generate(),
|
||||
// weights_type: WeightsType::LoRA, // to do parse multi part for this
|
||||
// weights_category: WeightsCategory::ImageGeneration,
|
||||
// title: upload_weights_request.file_name.as_deref(),
|
||||
// maybe_thumbnail_token: &ThumbnailToken::generate(),
|
||||
// description_markdown: "",
|
||||
// description_rendered_html: "",
|
||||
// creator_user_token: maybe_user_token.as_str(),
|
||||
// creator_ip_address: &ip_address,
|
||||
// creator_set_visibility,
|
||||
// maybe_last_update_user_token: "",
|
||||
// original_download_url: "",
|
||||
// original_filename: upload_weights_request.file_name.as_deref(),
|
||||
// file_size_bytes: file_size_bytes as i32,
|
||||
// file_checksum_sha2: hash,
|
||||
// private_bucket_hash: private_bucket_hash,
|
||||
// maybe_private_bucket_prefix: None,
|
||||
// maybe_private_bucket_extension: None,
|
||||
// cached_user_ratings_total_count: 0,
|
||||
// cached_user_ratings_positive_count: 0,
|
||||
// cached_user_ratings_negative_count: 0,
|
||||
// maybe_cached_user_ratings_ratio: None,
|
||||
// version: 0,
|
||||
// mysql_pool: &server_state.mysql_pool,
|
||||
// }).await
|
||||
// .map_err(|e| {
|
||||
// error!("Error creating weights upload: {:?}", e);
|
||||
// UploadError::ServerError
|
||||
// })?;
|
||||
|
||||
// info!("new weights upload id: {} token: {:?}", record_id, &token);
|
||||
|
||||
// Ok()
|
||||
// }
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Path;
|
||||
use log::{error, log, warn};
|
||||
use enums::by_table::audit_logs::audit_log_entity_action::AuditLogEntityAction::Delete;
|
||||
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
|
||||
use mysql_queries::queries::model_weights::delete_weights::{
|
||||
delete_weights_as_user,
|
||||
delete_weights_as_mod,
|
||||
undelete_weights_as_user,
|
||||
undelete_weights_as_mod
|
||||
};
|
||||
use mysql_queries::queries::model_weights::get_weight::get_weight_by_token;
|
||||
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
use crate::http_server::web_utils::response_success_helpers::simple_json_success;
|
||||
use crate::server_state::ServerState;
|
||||
use crate::util::delete_role_disambiguation::{delete_role_disambiguation, DeleteRole};
|
||||
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Deserialize,ToSchema)]
|
||||
pub struct DeleteWeightPathInfo {
|
||||
weight_token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize,ToSchema)]
|
||||
pub struct DeleteWeightRequest {
|
||||
set_delete: bool,
|
||||
as_mod: bool
|
||||
}
|
||||
|
||||
|
||||
// =============== Error Response ===============
|
||||
|
||||
#[derive(Debug, Serialize,ToSchema)]
|
||||
pub enum DeleteWeightError {
|
||||
BadInput(String),
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for DeleteWeightError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
DeleteWeightError::BadInput(_) => StatusCode::BAD_REQUEST,
|
||||
DeleteWeightError::NotFound => StatusCode::NOT_FOUND,
|
||||
DeleteWeightError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
DeleteWeightError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
serialize_as_json_error(self)
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for DeleteWeightError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/v1/weights/weight/{weight_token}",
|
||||
responses(
|
||||
(status = 200, description = "Success Delete", body = SimpleGenericJsonSuccess),
|
||||
(status = 400, description = "Bad input", body = DeleteWeightError),
|
||||
(status = 401, description = "Not authorized", body = DeleteWeightError),
|
||||
(status = 500, description = "Server error", body = DeleteWeightError),
|
||||
),
|
||||
params(
|
||||
("request" = DeleteWeightRequest, description = "Payload for Request"),
|
||||
("path" = DeleteWeightPathInfo, description = "Path for Request")
|
||||
)
|
||||
)]
|
||||
pub async fn delete_weight_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<DeleteWeightPathInfo>,
|
||||
request: web::Json<DeleteWeightRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, DeleteWeightError>{
|
||||
let maybe_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);
|
||||
DeleteWeightError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
warn!("not logged in");
|
||||
return Err(DeleteWeightError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
let weight_token = path.weight_token.clone();
|
||||
let is_mod = user_session.can_ban_users;
|
||||
|
||||
let weight_lookup_result = get_weight_by_token(
|
||||
&ModelWeightToken::new(weight_token.clone()),
|
||||
is_mod,
|
||||
&server_state.mysql_pool,
|
||||
).await;
|
||||
|
||||
let weight = match weight_lookup_result {
|
||||
Ok(Some(weight)) => weight,
|
||||
Ok(None) => {
|
||||
warn!("Weight not found: {:?}", weight_token);
|
||||
return Err(DeleteWeightError::NotFound);
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("Error looking up weight: {:?}", err);
|
||||
return Err(DeleteWeightError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let is_creator = weight.creator_user_token.to_string() == user_session.user_token;
|
||||
|
||||
if !is_creator && !is_mod {
|
||||
warn!("user is not allowed to delete this weight: {}", user_session.user_token);
|
||||
return Err(DeleteWeightError::NotAuthorized);
|
||||
}
|
||||
|
||||
let delete_role = delete_role_disambiguation(is_mod, is_creator, Some(request.as_mod));
|
||||
|
||||
let query_result = if request.set_delete {
|
||||
match delete_role {
|
||||
DeleteRole::ErrorDoNotDelete => {
|
||||
warn!("user is not allowed to delete weights: {}", user_session.user_token);
|
||||
return Err(DeleteWeightError::NotAuthorized);
|
||||
}
|
||||
DeleteRole::AsUser => {
|
||||
delete_weights_as_user(
|
||||
&ModelWeightToken::new_from_str(&path.weight_token),
|
||||
&server_state.mysql_pool
|
||||
).await
|
||||
}
|
||||
DeleteRole::AsMod => {
|
||||
delete_weights_as_mod(
|
||||
&ModelWeightToken::new_from_str(&path.weight_token),
|
||||
&server_state.mysql_pool
|
||||
).await
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match delete_role {
|
||||
DeleteRole::ErrorDoNotDelete => {
|
||||
warn!("user is not allowed to undelete weights: {}", user_session.user_token);
|
||||
return Err(DeleteWeightError::NotAuthorized);
|
||||
}
|
||||
DeleteRole::AsUser => {
|
||||
undelete_weights_as_user(
|
||||
&ModelWeightToken::new_from_str(&path.weight_token),
|
||||
&server_state.mysql_pool
|
||||
).await
|
||||
}
|
||||
DeleteRole::AsMod => {
|
||||
undelete_weights_as_user(
|
||||
&ModelWeightToken::new_from_str(&path.weight_token),
|
||||
&server_state.mysql_pool
|
||||
).await
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match query_result {
|
||||
Ok(_) => {},
|
||||
Err(err) => {
|
||||
warn!("Update weight mod approval status DB error: {:?}", err);
|
||||
return Err(DeleteWeightError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(simple_json_success())
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::web::BytesMut;
|
||||
use futures::TryStreamExt;
|
||||
use log::warn;
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::weights::ModelWeightToken;
|
||||
|
||||
use crate::http_server::web_utils::read_multipart_field_bytes::{checked_read_multipart_bytes, read_multipart_field_as_text};
|
||||
|
||||
pub struct WeightUploadRequest {
|
||||
pub uuid_idempotency_token: Option<String>,
|
||||
pub model_weights_token: Option<String>,
|
||||
pub file_name: Option<String>,
|
||||
pub file_bytes: Option<BytesMut>,
|
||||
pub media_source: MediaSource,
|
||||
}
|
||||
|
||||
/// Where the frontend tells us the file came from.
|
||||
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||
pub enum MediaSource {
|
||||
Unknown,
|
||||
UserFile,
|
||||
/// Eg. the web audio API in Javascript.
|
||||
UserDeviceApi,
|
||||
}
|
||||
|
||||
/// Pull common parts out of multipart media HTTP requests, typically for handling file uploads.
|
||||
pub async fn drain_multipart_request(mut multipart_payload: Multipart) -> AnyhowResult<WeightUploadRequest> {
|
||||
let mut uuid_idempotency_token = None;
|
||||
let mut model_weight_token = None;
|
||||
let mut file_bytes = None;
|
||||
let mut file_name = None;
|
||||
let mut media_source = None;
|
||||
|
||||
while let Ok(Some(mut field)) = multipart_payload.try_next().await {
|
||||
let mut field_name = None;
|
||||
let mut field_filename = None;
|
||||
|
||||
if let content_disposition = field.content_disposition() {
|
||||
field_name = content_disposition.get_name()
|
||||
.map(|s| s.to_string());
|
||||
field_filename = content_disposition.get_filename() // NB: Only used for the file bytes.
|
||||
.map(|s| s.to_string());
|
||||
}
|
||||
|
||||
match field_name.as_deref() {
|
||||
Some("uuid_idempotency_token") => {
|
||||
uuid_idempotency_token = read_multipart_field_as_text(&mut field).await
|
||||
.map_err(|e| {
|
||||
warn!("Error reading idempotency token: {:}", e);
|
||||
e
|
||||
})?;
|
||||
},
|
||||
Some("dataset_token") => {
|
||||
model_weight_token = read_multipart_field_as_text(&mut field).await
|
||||
.map_err(|e| {
|
||||
warn!("Error reading dataset token: {:}", e);
|
||||
e
|
||||
})?;
|
||||
},
|
||||
Some("file") => {
|
||||
file_name = field_filename.clone();
|
||||
file_bytes = checked_read_multipart_bytes(&mut field).await
|
||||
.map_err(|e| {
|
||||
warn!("Error reading audio upload: {:}", e);
|
||||
e
|
||||
})?;
|
||||
},
|
||||
Some("source") => {
|
||||
media_source = read_multipart_field_as_text(&mut field).await
|
||||
.map_err(|e| {
|
||||
warn!("Error reading source: {:}", e);
|
||||
e
|
||||
})?;
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
let media_source = match media_source.as_deref() {
|
||||
Some("device") => MediaSource::UserDeviceApi,
|
||||
Some("file") => MediaSource::UserFile,
|
||||
_ => {
|
||||
if file_name.as_deref() == Some("blob") {
|
||||
MediaSource::UserDeviceApi
|
||||
} else {
|
||||
MediaSource::Unknown
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(UploadSampleRequest {
|
||||
uuid_idempotency_token,
|
||||
dataset_token,
|
||||
file_name,
|
||||
file_bytes,
|
||||
media_source,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
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::common::visibility::Visibility;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
|
||||
use crate::server_state::ServerState;
|
||||
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use mysql_queries::queries::model_weights::get_weight::get_weight_by_token;
|
||||
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use enums::by_table::model_weights::weights_category::WeightsCategory;
|
||||
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Serialize, Clone, ToSchema)]
|
||||
pub struct GetWeightResponse {
|
||||
success: bool,
|
||||
weight_token: ModelWeightToken,
|
||||
title: String,
|
||||
weights_type: WeightsType,
|
||||
weights_category: WeightsCategory,
|
||||
maybe_thumbnail_token: Option<String>,
|
||||
description_markdown: String,
|
||||
description_rendered_html: String,
|
||||
creator_set_visibility: Visibility,
|
||||
|
||||
file_size_bytes: i32,
|
||||
file_checksum_sha2: String,
|
||||
|
||||
cached_user_ratings_negative_count: u32,
|
||||
cached_user_ratings_positive_count: u32,
|
||||
cached_user_ratings_total_count: u32,
|
||||
|
||||
maybe_cached_user_ratings_ratio: Option<f32>,
|
||||
cached_user_ratings_last_updated_at: DateTime<Utc>,
|
||||
|
||||
version: i32,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize,ToSchema)]
|
||||
pub struct GetWeightPathInfo {
|
||||
weight_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug,ToSchema)]
|
||||
pub enum GetWeightError {
|
||||
NotAuthorized,
|
||||
NotFound,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl fmt::Display for GetWeightError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for GetWeightError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
GetWeightError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
GetWeightError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
GetWeightError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/weights/weight/{weight_token}",
|
||||
responses(
|
||||
(status = 200, description = "Success Update", body = GetWeightResponse),
|
||||
(status = 400, description = "Bad input", body = GetWeightError),
|
||||
(status = 401, description = "Not authorized", body = GetWeightError),
|
||||
(status = 500, description = "Server error", body = GetWeightError),
|
||||
),
|
||||
params(
|
||||
("path" = GetWeightPathInfo, description = "Path for Request")
|
||||
)
|
||||
)]
|
||||
pub async fn get_weight_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<GetWeightPathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, GetWeightError> {
|
||||
|
||||
let maybe_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);
|
||||
GetWeightError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
warn!("not logged in");
|
||||
return Err(GetWeightError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
let weight_token = path.weight_token.clone();
|
||||
let creator_user_token = user_session.user_token.clone();
|
||||
let is_mod = user_session.can_ban_users;
|
||||
|
||||
let weight_lookup_result = get_weight_by_token(
|
||||
&ModelWeightToken::new(weight_token.clone()),
|
||||
is_mod,
|
||||
&server_state.mysql_pool,
|
||||
).await;
|
||||
|
||||
let weight = match weight_lookup_result {
|
||||
Ok(Some(weight)) => weight,
|
||||
Ok(None) => {
|
||||
warn!("Weight not found: {:?}", weight_token);
|
||||
return Err(GetWeightError::NotFound);
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("Error looking up weight: {:?}", err);
|
||||
return Err(GetWeightError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
// if the weight is private, only the creator can view it
|
||||
let is_private = weight.creator_set_visibility == Visibility::Private;
|
||||
|
||||
if is_private && creator_user_token.as_str() != &user_session.user_token {
|
||||
warn!("user is not allowed to view this weight: {}", user_session.user_token);
|
||||
return Err(GetWeightError::NotAuthorized);
|
||||
}
|
||||
|
||||
|
||||
let response = GetWeightResponse {
|
||||
success: true,
|
||||
weight_token: weight.token,
|
||||
title: weight.title,
|
||||
weights_type: weight.weights_type,
|
||||
weights_category: weight.weights_category,
|
||||
maybe_thumbnail_token: weight.maybe_thumbnail_token,
|
||||
description_markdown: weight.description_markdown,
|
||||
description_rendered_html: weight.description_rendered_html,
|
||||
creator_set_visibility: weight.creator_set_visibility,
|
||||
file_size_bytes: weight.file_size_bytes,
|
||||
file_checksum_sha2: weight.file_checksum_sha2,
|
||||
cached_user_ratings_negative_count: weight.cached_user_ratings_negative_count,
|
||||
cached_user_ratings_positive_count: weight.cached_user_ratings_positive_count,
|
||||
cached_user_ratings_total_count: weight.cached_user_ratings_total_count,
|
||||
maybe_cached_user_ratings_ratio: weight.maybe_cached_user_ratings_ratio,
|
||||
cached_user_ratings_last_updated_at: weight.cached_user_ratings_last_updated_at,
|
||||
version: weight.version,
|
||||
created_at: weight.created_at
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| GetWeightError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
use std::sync::Arc;
|
||||
use actix_web::web::Query;
|
||||
use actix_web::{ HttpRequest, HttpResponse, web };
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use chrono::{ DateTime, Utc };
|
||||
use log::{ info, warn };
|
||||
use rand::Rng;
|
||||
use enums::common::visibility::Visibility;
|
||||
use mysql_queries::queries::model_weights::list_weights_query_builder::ListWeightsQueryBuilder;
|
||||
|
||||
use enums::by_table::model_weights::{
|
||||
weights_types::WeightsType,
|
||||
weights_category::WeightsCategory,
|
||||
};
|
||||
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
use crate::http_server::common_responses::user_details_lite::{UserDetailsLight,DefaultAvatarInfo};
|
||||
use crate::server_state::ServerState;
|
||||
|
||||
use utoipa::ToSchema;
|
||||
|
||||
|
||||
#[derive(Deserialize,ToSchema)]
|
||||
pub struct ListAvailableWeightsQuery {
|
||||
pub sort_ascending: Option<bool>,
|
||||
pub page_size: u16,
|
||||
pub page_index : u16,
|
||||
pub username: Option<String>,
|
||||
pub weights_type: Option<String>,
|
||||
pub weights_category: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize,ToSchema)]
|
||||
pub struct ListAvailableWeightsSuccessResponse {
|
||||
pub success: bool,
|
||||
pub weights: Vec<ModelWeightForList>,
|
||||
pub page_size: u16,
|
||||
pub page_index : u16,
|
||||
pub cursor_next: Option<String>,
|
||||
pub cursor_previous: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize,ToSchema)]
|
||||
pub struct ModelWeightForList {
|
||||
pub weight_token: ModelWeightToken,
|
||||
|
||||
pub weights_type: WeightsType,
|
||||
pub weights_category: WeightsCategory,
|
||||
|
||||
pub title: String,
|
||||
|
||||
pub maybe_thumbnail_token: Option<String>,
|
||||
|
||||
pub description_markdown: String,
|
||||
pub description_rendered_html: String,
|
||||
|
||||
pub creator_user_token: UserToken,
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
pub file_size_bytes: i32,
|
||||
pub file_checksum_sha2: String,
|
||||
|
||||
pub cached_user_ratings_total_count: u32,
|
||||
pub cached_user_ratings_positive_count: u32,
|
||||
pub cached_user_ratings_negative_count: u32,
|
||||
pub maybe_cached_user_ratings_ratio: Option<f32>,
|
||||
|
||||
pub version: i32,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
|
||||
pub creator_username: String,
|
||||
pub creator_display_name: String,
|
||||
pub creator_email_gravatar_hash: String,
|
||||
|
||||
// additional fields to be added when tables are around
|
||||
pub likes: u32,
|
||||
pub bookmarks: bool,
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug,ToSchema)]
|
||||
pub enum ListWeightError {
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ListWeightError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for ListWeightError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
ListWeightError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
ListWeightError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/weights/list",
|
||||
responses(
|
||||
(status = 200, description = "List Weights", body = ListAvailableWeightsSuccessResponse),
|
||||
(status = 401, description = "Not authorized", body = ListWeightError),
|
||||
(status = 500, description = "Server error", body = ListWeightError),
|
||||
),
|
||||
params(
|
||||
("request" = ListAvailableWeightsQuery, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn list_available_weights_handler(
|
||||
http_request: HttpRequest,
|
||||
query: web::Query<ListAvailableWeightsQuery>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, impl ResponseError> {
|
||||
let maybe_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);
|
||||
ListWeightError::ServerError
|
||||
})?;
|
||||
|
||||
let mut is_mod = false;
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => {
|
||||
is_mod = session.can_ban_users;
|
||||
session
|
||||
}
|
||||
None => {
|
||||
info!("not logged in");
|
||||
return Err(ListWeightError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
let limit = query.page_size;
|
||||
|
||||
let sort_ascending = query.sort_ascending.unwrap_or(false);
|
||||
|
||||
let include_user_hidden = is_mod;
|
||||
|
||||
let mut weights_category: Option<WeightsCategory> = None;
|
||||
let mut weights_type: Option<WeightsType> = None;
|
||||
|
||||
if let Some(weights_category_string) = query.weights_category.as_ref() {
|
||||
let result = WeightsCategory::from_str(&weights_category_string);
|
||||
match result {
|
||||
Ok(category) => {
|
||||
weights_category = Some(category)
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("invalid weights_category: {:?}", e);
|
||||
weights_category = None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(weights_type_string) = query.weights_type.as_ref() {
|
||||
let result = WeightsType::from_str(&weights_type_string);
|
||||
match result {
|
||||
Ok(wtype) => {
|
||||
weights_type = Some(wtype)
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("invalid weights_type: {:?}", e);
|
||||
weights_type = None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut query_builder = ListWeightsQueryBuilder::new()
|
||||
.sort_ascending(sort_ascending)
|
||||
.weights_category(weights_category)
|
||||
.weights_type(weights_type)
|
||||
.scope_creator_username(None)
|
||||
.include_user_hidden(include_user_hidden)
|
||||
.include_user_deleted_results(is_mod)
|
||||
.include_mod_deleted_results(is_mod)
|
||||
.limit(limit)
|
||||
.offset(Some((query.page_index as u64) * (query.page_size as u64)));
|
||||
|
||||
let query_results = query_builder.perform_query_for_page(&server_state.mysql_pool).await;
|
||||
|
||||
let weights_page = match query_results {
|
||||
Ok(results) => results,
|
||||
Err(e) => {
|
||||
warn!("Query error: {:?}", e);
|
||||
return Err(ListWeightError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let cursor_next = if let Some(id) = weights_page.last_id {
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)
|
||||
.map_err(|e| {
|
||||
warn!("crypto error: {:?}", e);
|
||||
ListWeightError::ServerError
|
||||
})?;
|
||||
Some(cursor)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cursor_previous = if let Some(id) = weights_page.first_id {
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)
|
||||
.map_err(|e| {
|
||||
warn!("crypto error: {:?}", e);
|
||||
ListWeightError::ServerError
|
||||
})?;
|
||||
Some(cursor)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let random_float = rng.gen_range(0.0..1.0);
|
||||
let random_bool = random_float >= 0.5;
|
||||
|
||||
// generate parse a response
|
||||
let response = ListAvailableWeightsSuccessResponse {
|
||||
success: true,
|
||||
weights: weights_page.weights.into_iter()
|
||||
.map(|weights| ModelWeightForList {
|
||||
weight_token: weights.token,
|
||||
title: weights.title,
|
||||
weights_type: weights.weights_type,
|
||||
weights_category: weights.weights_category,
|
||||
|
||||
maybe_thumbnail_token:weights.maybe_thumbnail_token,
|
||||
description_markdown: weights.description_markdown,
|
||||
description_rendered_html: weights.description_rendered_html,
|
||||
|
||||
creator_user_token: weights.creator_user_token,
|
||||
creator_set_visibility: weights.creator_set_visibility,
|
||||
|
||||
file_size_bytes:weights.file_size_bytes,
|
||||
file_checksum_sha2: weights.file_checksum_sha2,
|
||||
|
||||
cached_user_ratings_total_count: weights.cached_user_ratings_total_count,
|
||||
cached_user_ratings_positive_count: weights.cached_user_ratings_positive_count,
|
||||
cached_user_ratings_negative_count: weights.cached_user_ratings_negative_count,
|
||||
maybe_cached_user_ratings_ratio: weights.maybe_cached_user_ratings_ratio,
|
||||
|
||||
version: weights.version,
|
||||
|
||||
created_at: weights.created_at,
|
||||
updated_at: weights.updated_at,
|
||||
|
||||
creator_username: weights.creator_username,
|
||||
creator_display_name: weights.creator_display_name,
|
||||
creator_email_gravatar_hash: weights.creator_email_gravatar_hash,
|
||||
|
||||
// TODO: FIX THIS when we align again.
|
||||
bookmarks: random_bool,
|
||||
likes: rng.gen_range(0..1000),
|
||||
}).collect::<Vec<_>>(),
|
||||
cursor_next,
|
||||
cursor_previous,
|
||||
page_index: query.page_index,
|
||||
page_size: query.page_size
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| ListWeightError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok().content_type("application/json").body(body))
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
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::common::visibility::Visibility;
|
||||
use mysql_queries::queries::model_weights::list_weights_by_user::{list_weights_by_creator_username, WeightsJoinUserRecord};
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
|
||||
use crate::server_state::ServerState;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Serialize, Clone,ToSchema)]
|
||||
pub struct Weight {
|
||||
weight_token: ModelWeightToken,
|
||||
title: String,
|
||||
|
||||
creator: UserDetailsLight,
|
||||
creator_set_visibility: Visibility,
|
||||
|
||||
maybe_thumbnail_token: Option<String>,
|
||||
|
||||
description_markdown: String,
|
||||
description_rendered_html: String,
|
||||
|
||||
file_size_bytes: i32,
|
||||
file_checksum_sha2: String,
|
||||
cached_user_ratings_total_count: u32,
|
||||
cached_user_ratings_positive_count: u32,
|
||||
cached_user_ratings_negative_count: u32,
|
||||
maybe_cached_user_ratings_ratio: Option<f32>,
|
||||
cached_user_ratings_last_updated_at: DateTime<Utc>,
|
||||
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize,ToSchema)]
|
||||
pub struct ListWeightsByUserSuccessResponse {
|
||||
pub success: bool,
|
||||
pub weights: Vec<Weight>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize,ToSchema)]
|
||||
pub struct ListWeightsByUserPathInfo {
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug,ToSchema)]
|
||||
pub enum ListWeightsByUserError {
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl fmt::Display for ListWeightsByUserError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for ListWeightsByUserError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
ListWeightsByUserError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
ListWeightsByUserError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/weights/by_user/{username}",
|
||||
responses(
|
||||
(status = 200, description = "List Weights by user", body = ListWeightsByUserSuccessResponse),
|
||||
(status = 401, description = "Not authorized", body = ListWeightsByUserError),
|
||||
(status = 500, description = "Server error", body = ListWeightsByUserError),
|
||||
),
|
||||
params(
|
||||
("path" = ListWeightsByUserPathInfo, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn list_weights_by_user_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<ListWeightsByUserPathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, ListWeightsByUserError> {
|
||||
|
||||
let maybe_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);
|
||||
ListWeightsByUserError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
warn!("not logged in");
|
||||
return Err(ListWeightsByUserError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
let username = path.username.as_ref();
|
||||
let creator_user_token = user_session.user_token.clone();
|
||||
let is_mod = user_session.can_ban_users;
|
||||
|
||||
let query_results = list_weights_by_creator_username(
|
||||
&server_state.mysql_pool,
|
||||
&username,
|
||||
is_mod,
|
||||
).await.map_err(|e| {
|
||||
warn!("Error querying for weights: {:?}", e);
|
||||
ListWeightsByUserError::ServerError
|
||||
});
|
||||
|
||||
let weights = match query_results {
|
||||
Ok(weights) => weights,
|
||||
Err(e) => {
|
||||
warn!("Error querying for weights: {:?}", e);
|
||||
return Err(ListWeightsByUserError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let weights:Vec<Weight> = weights.into_iter().map(|weight| {
|
||||
Weight {
|
||||
weight_token: weight.token,
|
||||
title: weight.title,
|
||||
creator: UserDetailsLight::from_db_fields(
|
||||
&weight.creator_user_token,
|
||||
&weight.creator_username,
|
||||
&weight.creator_display_name,
|
||||
&weight.creator_email_gravatar_hash,
|
||||
),
|
||||
maybe_thumbnail_token: weight.maybe_thumbnail_token,
|
||||
description_markdown: weight.description_markdown,
|
||||
description_rendered_html: weight.description_rendered_html,
|
||||
file_size_bytes: weight.file_size_bytes,
|
||||
file_checksum_sha2: weight.file_checksum_sha2,
|
||||
cached_user_ratings_total_count: weight.cached_user_ratings_total_count,
|
||||
cached_user_ratings_positive_count: weight.cached_user_ratings_positive_count,
|
||||
cached_user_ratings_negative_count: weight.cached_user_ratings_negative_count,
|
||||
maybe_cached_user_ratings_ratio: weight.maybe_cached_user_ratings_ratio,
|
||||
cached_user_ratings_last_updated_at: weight.cached_user_ratings_last_updated_at,
|
||||
creator_set_visibility: weight.creator_set_visibility,
|
||||
created_at: weight.created_at,
|
||||
updated_at: weight.updated_at,
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let final_weights:Vec<Weight>;
|
||||
|
||||
// if it's not the user ... then only show public weights else show private and public
|
||||
if (creator_user_token != user_session.user_token) {
|
||||
final_weights = weights.into_iter().filter(|weight| {
|
||||
weight.creator_set_visibility == Visibility::Public
|
||||
}).collect();
|
||||
|
||||
}
|
||||
else {
|
||||
final_weights = weights;
|
||||
}
|
||||
|
||||
|
||||
let response: ListWeightsByUserSuccessResponse = ListWeightsByUserSuccessResponse {
|
||||
success: true,
|
||||
weights: final_weights,
|
||||
};
|
||||
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| ListWeightsByUserError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod get_weight;
|
||||
pub mod delete_weight;
|
||||
pub mod update_weight;
|
||||
pub mod list_weights_by_user;
|
||||
pub mod list_available_weights;
|
||||
// pub mod create_weight; this is a private api call internally
|
||||
// pub mod drain_multipart_weights_request;
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::{ HttpRequest, HttpResponse, web };
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Path;
|
||||
use log::{ error, warn };
|
||||
|
||||
use enums::common::visibility::Visibility;
|
||||
use http_server_common::response::response_success_helpers::simple_json_success;
|
||||
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
|
||||
use mysql_queries::queries::model_weights::get_weight::get_weight_by_token;
|
||||
use mysql_queries::queries::model_weights::update_weight::{ update_weights, UpdateWeightArgs };
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
use crate::server_state::ServerState;
|
||||
use utoipa::ToSchema;
|
||||
/// TODO will eventually be polymorphic
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct UpdateWeightRequest {
|
||||
pub title: Option<String>,
|
||||
pub thumbnail_token: Option<String>,
|
||||
pub description_markdown: Option<String>,
|
||||
pub description_rendered_html: Option<String>,
|
||||
pub weight_type: Option<String>,
|
||||
pub weight_category: Option<String>,
|
||||
pub visibility: Option<Visibility>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct UpdateWeightResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
/// For the URL PathInfo
|
||||
#[derive(Deserialize,ToSchema)]
|
||||
pub struct UpdateWeightPathInfo {
|
||||
weight_token: String,
|
||||
}
|
||||
|
||||
// =============== Error Response ===============
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub enum UpdateWeightError {
|
||||
BadInput(String),
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for UpdateWeightError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
UpdateWeightError::BadInput(_) => StatusCode::BAD_REQUEST,
|
||||
UpdateWeightError::NotFound => StatusCode::NOT_FOUND,
|
||||
UpdateWeightError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
UpdateWeightError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
serialize_as_json_error(self)
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for UpdateWeightError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Handler ===============
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/v1/weights/weight/{weight_token}",
|
||||
responses(
|
||||
(status = 200, description = "Success Update", body = SimpleGenericJsonSuccess),
|
||||
(status = 400, description = "Bad input", body = UpdateWeightError),
|
||||
(status = 401, description = "Not authorized", body = UpdateWeightError),
|
||||
(status = 500, description = "Server error", body = UpdateWeightError),
|
||||
),
|
||||
params(
|
||||
("request" = UpdateWeightRequest, description = "Payload for Request"),
|
||||
("path" = UpdateWeightPathInfo, description = "Path for Request")
|
||||
)
|
||||
)]
|
||||
pub async fn update_weight_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<UpdateWeightPathInfo>,
|
||||
request: web::Json<UpdateWeightRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, UpdateWeightError> {
|
||||
let my_sql_pool = &server_state.mysql_pool;
|
||||
|
||||
let maybe_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);
|
||||
UpdateWeightError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
warn!("not logged in");
|
||||
return Err(UpdateWeightError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
let weight_token = path.weight_token.clone();
|
||||
|
||||
// TODO wouldn't we want to instead use a function that will query the DB for the user and determine if they are a mod?
|
||||
let is_mod = user_session.can_ban_users;
|
||||
|
||||
let weight_lookup_result = get_weight_by_token(
|
||||
&ModelWeightToken::new(weight_token.clone()),
|
||||
is_mod,
|
||||
&server_state.mysql_pool
|
||||
).await;
|
||||
|
||||
let weight = match weight_lookup_result {
|
||||
Ok(Some(weight)) => weight,
|
||||
Ok(None) => {
|
||||
warn!("Weight not found: {:?}", weight_token);
|
||||
return Err(UpdateWeightError::NotFound);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Error looking up weight: {:?}", err);
|
||||
return Err(UpdateWeightError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let is_creator = weight.creator_user_token.to_string() == user_session.user_token;
|
||||
|
||||
if !is_creator && !is_mod {
|
||||
warn!("user is not allowed to edit this weight: {}", user_session.user_token);
|
||||
return Err(UpdateWeightError::NotAuthorized);
|
||||
}
|
||||
|
||||
let query_result = update_weights(UpdateWeightArgs {
|
||||
weight_token: &ModelWeightToken::new(path.weight_token.clone()),
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
title: request.title.as_deref(),
|
||||
maybe_thumbnail_token: request.thumbnail_token.as_deref(),
|
||||
description_markdown: request.description_markdown.as_deref(),
|
||||
description_rendered_html: request.description_rendered_html.as_deref(),
|
||||
creator_set_visibility: request.visibility.as_ref(),
|
||||
weights_type: request.weight_type.as_deref().map(|s| s.to_string()),
|
||||
weights_category: request.weight_category.as_deref().map(|s| s.to_string()),
|
||||
}).await;
|
||||
|
||||
match query_result {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
warn!("Update Weight DB error: {:?}", err);
|
||||
return Err(UpdateWeightError::ServerError);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(simple_json_success())
|
||||
}
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use enums::by_table::model_weights::weights_category::WeightsCategory;
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use mysql_queries::queries::model_weights::create_weight::{self, create_weight};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
use crate::http_server::endpoints::media_uploads::common::upload_error::UploadError;
|
||||
use crate::server_state::ServerState;
|
||||
|
||||
use log::{error, info, warn};
|
||||
|
||||
use buckets::public::weight_uploads::original_file::WeightUploadOriginalFilePath;
|
||||
use enums::by_table::media_uploads::media_upload_source::MediaUploadSource;
|
||||
use enums::by_table::media_uploads::media_upload_type::MediaUploadType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use media::decode_basic_audio_info::decode_basic_audio_bytes_info;
|
||||
use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes;
|
||||
use mysql_queries::queries::media_uploads::get_media_upload_by_uuid::get_media_upload_by_uuid_with_connection;
|
||||
use mysql_queries::queries::media_uploads::insert_media_upload::{Args, insert_media_upload};
|
||||
|
||||
use crate::http_server::endpoints::media_uploads::common::drain_multipart_request::{drain_multipart_request, MediaSource};
|
||||
use crate::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub async fn upload_weights_handler(
|
||||
http_request: HttpRequest,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
mut multipart_payload: Multipart,
|
||||
) -> Result<HttpResponse, UploadError> {
|
||||
|
||||
let response = handle_weight_upload(
|
||||
&http_request,
|
||||
&server_state,
|
||||
multipart_payload,
|
||||
&ALLOWED_MIME_TYPES).await?;
|
||||
|
||||
// send a body with the required payload along side the multipart upload
|
||||
|
||||
let weight_upload_token = response.to_model_token();
|
||||
|
||||
let response: UploadWeightsSuccessResponse = UploadWeightsSuccessResponse {
|
||||
success: true,
|
||||
upload_token: weight_upload_token,
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| UploadError::ServerError)?;
|
||||
|
||||
return Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body));
|
||||
}
|
||||
|
||||
pub enum SuccessCase {
|
||||
WeightAlreadyUploaded {
|
||||
existing_upload_token: ModelWeightToken,
|
||||
},
|
||||
WeightSuccessfullyUploaded {
|
||||
upload_token: ModelWeightToken,
|
||||
}
|
||||
}
|
||||
|
||||
impl SuccessCase {
|
||||
pub fn to_model_token(self) -> ModelWeightToken {
|
||||
match self {
|
||||
SuccessCase::WeightAlreadyUploaded { existing_upload_token } => existing_upload_token,
|
||||
SuccessCase::WeightSuccessfullyUploaded { upload_token } => upload_token,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_weight_upload(
|
||||
http_request: &HttpRequest,
|
||||
server_state: &web::Data<Arc<ServerState>>,
|
||||
mut multipart_payload: Multipart,
|
||||
allowed_mimetypes: &HashSet<&'static str>,
|
||||
) -> Result<SuccessCase, UploadError> {
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("MySql pool error: {:?}", err);
|
||||
UploadError::ServerError
|
||||
})?;
|
||||
|
||||
// ==================== READ SESSION ==================== //
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Session checker error: {:?}", e);
|
||||
UploadError::ServerError
|
||||
})?;
|
||||
|
||||
// ==================== RATE LIMIT ==================== //
|
||||
|
||||
let rate_limiter = match maybe_user_session {
|
||||
None => &server_state.redis_rate_limiters.logged_out,
|
||||
Some(ref user) => {
|
||||
if user.is_banned {
|
||||
return Err(UploadError::NotAuthorized);
|
||||
}
|
||||
&server_state.redis_rate_limiters.logged_in
|
||||
},
|
||||
};
|
||||
|
||||
if let Err(_err) = rate_limiter.rate_limit_request(&http_request) {
|
||||
return Err(UploadError::RateLimited);
|
||||
}
|
||||
|
||||
if let Err(_err) = server_state.redis_rate_limiters.model_upload.rate_limit_request(&http_request) {
|
||||
return Err(UploadError::RateLimited);
|
||||
}
|
||||
|
||||
// ==================== READ MULTIPART REQUEST ==================== //
|
||||
|
||||
let upload_weights_request = drain_multipart_request(multipart_payload)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// TODO: Error handling could be nicer.
|
||||
UploadError::BadInput("bad request".to_string())
|
||||
})?;
|
||||
|
||||
let uuid_idempotency_token = upload_weights_request.uuid_idempotency_token
|
||||
.ok_or(UploadError::BadInput("no uuid".to_string()))?;
|
||||
|
||||
let maybe_existing_upload =
|
||||
get_media_upload_by_uuid_with_connection(&uuid_idempotency_token, &mut mysql_connection)
|
||||
.await;
|
||||
|
||||
match maybe_existing_upload {
|
||||
Err(err) => {
|
||||
error!("Error checking for previous upload: {:?}", err);
|
||||
return Err(UploadError::ServerError);
|
||||
}
|
||||
Ok(Some(upload)) => {
|
||||
// File already uploaded and frontend didn't protect us.
|
||||
return Ok(SuccessCase::WeightAlreadyUploaded {
|
||||
existing_upload_token: upload.token,
|
||||
});
|
||||
}
|
||||
Ok(None) => {
|
||||
// Proceed.
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(reason) = validate_idempotency_token_format(&uuid_idempotency_token) {
|
||||
return Err(UploadError::BadInput(reason));
|
||||
}
|
||||
|
||||
let creator_set_visibility = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|user_session| user_session.preferred_tts_result_visibility) // TODO: We need a new type of visibility control.
|
||||
.unwrap_or(Visibility::default());
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
|
||||
let maybe_file_size_bytes = upload_weights_request.file_bytes
|
||||
.as_ref()
|
||||
.map(|bytes| bytes.len());
|
||||
|
||||
info!("Upload maybe filesize: {:?}", maybe_file_size_bytes);
|
||||
|
||||
let maybe_mimetype = upload_weights_request.file_bytes
|
||||
.as_ref()
|
||||
.map(|bytes| get_mimetype_for_bytes(bytes.as_ref()))
|
||||
.flatten();
|
||||
|
||||
|
||||
let bytes = match upload_weights_request.file_bytes {
|
||||
None => return Err(UploadError::BadInput("missing file contents".to_string())),
|
||||
Some(bytes) => bytes,
|
||||
};
|
||||
|
||||
let hash = sha256_hash_bytes(&bytes)
|
||||
.map_err(|io_error| {
|
||||
error!("Problem hashing bytes: {:?}", io_error);
|
||||
UploadError::ServerError
|
||||
})?;
|
||||
|
||||
let file_size_bytes = bytes.len();
|
||||
|
||||
let mut maybe_duration_millis = None;
|
||||
let mut maybe_codec_name = None;
|
||||
let mut media_upload_type = None;
|
||||
|
||||
if let Some(mimetype) = maybe_mimetype.as_deref() {
|
||||
|
||||
if !allowed_mimetypes.contains(mimetype) {
|
||||
// NB: Don't let our error message inject malicious strings
|
||||
let filtered_mimetype = mimetype
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii())
|
||||
.filter(|c| c.is_alphanumeric() || *c == '/')
|
||||
.collect::<String>();
|
||||
return Err(UploadError::BadInput(format!("unpermitted mime type: {}", &filtered_mimetype)));
|
||||
}
|
||||
media_upload_type = match mimetype {
|
||||
"binary/octet-stream" /* .bin */ => Some(MediaUploadType::Binary),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
// This is fine for now we just don't expose the path!
|
||||
let public_upload_path = WeightUploadOriginalFilePath::generate_new();
|
||||
|
||||
info!("Uploading media to bucket path: {}", public_upload_path.get_full_object_path_str());
|
||||
|
||||
// need to upload based on visibility
|
||||
server_state.public_bucket_client.upload_file_with_content_type(
|
||||
public_upload_path.get_full_object_path_str(),
|
||||
bytes.as_ref(),
|
||||
mime_type)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Upload media bytes to bucket error: {:?}", e);
|
||||
UploadError::ServerError
|
||||
})?;
|
||||
|
||||
let token = create_weight(CreateModelWeightsArgs {
|
||||
token: &ModelWeightToken::generate(),
|
||||
weights_type: WeightsType::LoRA, // to do parse multi part for this
|
||||
weights_category: WeightsCategory::ImageGeneration,
|
||||
title: upload_weights_request.file_name.as_deref(),
|
||||
maybe_thumbnail_token: &ThumbnailToken::generate(),
|
||||
description_markdown: "",
|
||||
description_rendered_html: "",
|
||||
creator_user_token: maybe_user_token.as_str(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
maybe_last_update_user_token: "",
|
||||
original_download_url: "",
|
||||
original_filename: upload_weights_request.file_name.as_deref(),
|
||||
file_size_bytes: file_size_bytes as i32,
|
||||
file_checksum_sha2: hash,
|
||||
private_bucket_hash: private_bucket_hash,
|
||||
maybe_private_bucket_prefix: None,
|
||||
maybe_private_bucket_extension: None,
|
||||
cached_user_ratings_total_count: 0,
|
||||
cached_user_ratings_positive_count: 0,
|
||||
cached_user_ratings_negative_count: 0,
|
||||
maybe_cached_user_ratings_ratio: None,
|
||||
version: 0,
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
}).await
|
||||
.map_err(|e| {
|
||||
error!("Error creating weights upload: {:?}", e);
|
||||
UploadError::ServerError
|
||||
})?;
|
||||
|
||||
info!("new weights upload id: {} token: {:?}", record_id, &token);
|
||||
|
||||
Ok(SuccessCase::WeightSuccessfullyUploaded {
|
||||
upload_token: token,
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use actix_web::HttpResponse;
|
||||
|
||||
#[derive(Serialize)]
|
||||
use utoipa::ToSchema;
|
||||
#[derive(Serialize,ToSchema)]
|
||||
pub struct SimpleGenericJsonSuccess {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
@@ -152,6 +152,13 @@ use crate::http_server::endpoints::w2l::list_user_w2l_templates::list_user_w2l_t
|
||||
use crate::http_server::endpoints::w2l::list_w2l_templates::list_w2l_templates_handler;
|
||||
use crate::http_server::endpoints::w2l::set_w2l_template_mod_approval::set_w2l_template_mod_approval_handler;
|
||||
|
||||
use crate::http_server::endpoints::weights::get_weight::get_weight_handler;
|
||||
use crate::http_server::endpoints::weights::delete_weight::delete_weight_handler;
|
||||
use crate::http_server::endpoints::weights::update_weight::update_weight_handler;
|
||||
use crate::http_server::endpoints::weights::list_available_weights::list_available_weights_handler;
|
||||
use crate::http_server::endpoints::weights::list_weights_by_user::list_weights_by_user_handler;
|
||||
|
||||
|
||||
pub fn add_routes<T, B> (app: App<T>, server_environment: ServerEnvironment) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
@@ -184,6 +191,9 @@ pub fn add_routes<T, B> (app: App<T>, server_environment: ServerEnvironment) ->
|
||||
app = add_subscription_routes(app); /* /v1/subscriptions/... */
|
||||
app = add_voice_designer_routes(app); /* /v1/voice_designer */
|
||||
|
||||
if server_environment == ServerEnvironment::Development {
|
||||
app = add_weights_routes(app); /* /v1/stubs/... */
|
||||
}
|
||||
// ==================== Comments ====================
|
||||
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
@@ -1231,3 +1241,33 @@ fn add_voice_designer_routes<T,B> (app:App<T>)-> App<T>
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== Weights ROUTES ====================
|
||||
fn add_weights_routes<T, B>(app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = ()
|
||||
>
|
||||
{
|
||||
app.service(
|
||||
web
|
||||
::scope("/v1/weights")
|
||||
//.route("/upload", web::post().to(upload_weights_handler))
|
||||
.service(
|
||||
web
|
||||
::resource("/weight/{weight_token}")
|
||||
.route(web::get().to(get_weight_handler))
|
||||
.route(web::post().to(update_weight_handler))
|
||||
.route(web::delete().to(delete_weight_handler))
|
||||
)
|
||||
.route("/by_user/{username}", web::get().to(list_weights_by_user_handler))
|
||||
.route(
|
||||
"/list",
|
||||
web::get().to(list_available_weights_handler)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user