diff --git a/Cargo.lock b/Cargo.lock index 92b6bab..6c13b3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3048,6 +3048,7 @@ dependencies = [ "sea-orm", "serde", "serde_json", + "strum 0.27.1", "thiserror 2.0.12", "time", "tokio", @@ -3103,7 +3104,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "strum", + "strum 0.26.3", "thiserror 2.0.12", "time", "tracing", @@ -3678,6 +3679,28 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.100", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 4786c02..07d02ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ time = "0.3.41" tokio = { version = "1.44.1", features = ["full"] } tower-http = { version = "0.6.2", features = ["fs"] } tower-sessions = "0.14.0" +strum = { version = "0.27.1", features = ["derive"] } [profile.release] strip = true diff --git a/README.md b/README.md index ff6f363..8044983 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,10 @@ Still very much an early WIP. ### Possible roadmap -- [ ] Caching +- [ ] Display thumbnails on post selection - [ ] Text media - [ ] Improve CSS - [ ] User management -- [ ] Display thumbnails on post selection - [ ] Testing - [ ] Lossless compression - [ ] Migrate to Cot...? diff --git a/src/lib.rs b/src/lib.rs index edb4fab..85e2c75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ pub(crate) mod config; pub(crate) mod entities; pub(crate) mod error; pub(crate) mod query; -pub(crate) mod rating; +pub(crate) mod tags; pub(crate) mod video; pub(crate) mod views; @@ -35,9 +35,6 @@ use crate::entities::{prelude::SameyUser, samey_user}; pub use crate::error::SameyError; use crate::views::*; -pub(crate) const NEGATIVE_PREFIX: &str = "-"; -pub(crate) const RATING_PREFIX: &str = "rating:"; - #[derive(rust_embed::Embed)] #[folder = "static/"] struct Asset; diff --git a/src/query.rs b/src/query.rs index e46c83a..3b75647 100644 --- a/src/query.rs +++ b/src/query.rs @@ -9,12 +9,13 @@ use sea_orm::{ }; use crate::{ - NEGATIVE_PREFIX, RATING_PREFIX, SameyError, + SameyError, auth::User, entities::{ prelude::{SameyPool, SameyPoolPost, SameyPost, SameyTag, SameyTagPost}, samey_pool, samey_pool_post, samey_post, samey_tag, samey_tag_post, }, + tags::{MEDIA_TYPE_PREFIX, NEGATIVE_PREFIX, RATING_PREFIX}, }; #[derive(Debug, FromQueryResult)] @@ -38,17 +39,23 @@ pub(crate) fn search_posts( let mut exclude_tags = HashSet::::new(); let mut include_ratings = HashSet::::new(); let mut exclude_ratings = HashSet::::new(); + let mut include_types = HashSet::::new(); + let mut exclude_types = HashSet::::new(); if let Some(tags) = tags { - for mut tag in tags.iter().map(|tag| tag.to_lowercase()) { - if tag.starts_with(NEGATIVE_PREFIX) { - if tag.as_str()[NEGATIVE_PREFIX.len()..].starts_with(RATING_PREFIX) { - exclude_ratings - .insert(tag.split_off(NEGATIVE_PREFIX.len() + RATING_PREFIX.len())); + for tag in tags.iter().map(|tag| tag.to_lowercase()) { + if let Some(negative_tag) = tag.strip_prefix(NEGATIVE_PREFIX) { + if let Some(negative_rating_tag) = negative_tag.strip_prefix(RATING_PREFIX) { + exclude_ratings.insert(negative_rating_tag.into()); + } else if let Some(negative_type_tag) = negative_tag.strip_prefix(MEDIA_TYPE_PREFIX) + { + exclude_types.insert(negative_type_tag.into()); } else { - exclude_tags.insert(tag.split_off(NEGATIVE_PREFIX.len())); + exclude_tags.insert(negative_tag.into()); } - } else if tag.starts_with(RATING_PREFIX) { - include_ratings.insert(tag.split_off(RATING_PREFIX.len())); + } else if let Some(rating_tag) = tag.strip_prefix(RATING_PREFIX) { + include_ratings.insert(rating_tag.into()); + } else if let Some(type_tag) = tag.strip_prefix(MEDIA_TYPE_PREFIX) { + include_types.insert(type_tag.into()); } else { include_tags.insert(tag); } @@ -81,6 +88,12 @@ pub(crate) fn search_posts( if !exclude_ratings.is_empty() { query = query.filter(samey_post::Column::Rating.is_not_in(exclude_ratings)) } + if !include_types.is_empty() { + query = query.filter(samey_post::Column::MediaType.is_in(include_types)) + } + if !exclude_types.is_empty() { + query = query.filter(samey_post::Column::MediaType.is_not_in(exclude_types)) + } query } else { let mut query = SameyPost::find() @@ -147,6 +160,12 @@ pub(crate) fn search_posts( if !exclude_ratings.is_empty() { query = query.filter(samey_post::Column::Rating.is_not_in(exclude_ratings)) } + if !include_types.is_empty() { + query = query.filter(samey_post::Column::MediaType.is_in(include_types)) + } + if !exclude_types.is_empty() { + query = query.filter(samey_post::Column::MediaType.is_not_in(exclude_types)) + } query }; diff --git a/src/rating.rs b/src/rating.rs deleted file mode 100644 index b95b44c..0000000 --- a/src/rating.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::fmt::Display; - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum Rating { - Unrated, - Safe, - Questionable, - Explicit, -} - -impl From for Rating { - fn from(value: String) -> Self { - match value.as_ref() { - "s" => Self::Safe, - "q" => Self::Questionable, - "e" => Self::Explicit, - _ => Self::Unrated, - } - } -} - -impl Display for Rating { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Rating::Unrated => f.write_str("Unrated"), - Rating::Safe => f.write_str("Safe"), - Rating::Questionable => f.write_str("Questionable"), - Rating::Explicit => f.write_str("Explicit"), - } - } -} diff --git a/src/tags.rs b/src/tags.rs new file mode 100644 index 0000000..3954e8f --- /dev/null +++ b/src/tags.rs @@ -0,0 +1,23 @@ +pub(crate) const NEGATIVE_PREFIX: &str = "-"; +pub(crate) const RATING_PREFIX: &str = "rating:"; +pub(crate) const MEDIA_TYPE_PREFIX: &str = "type:"; + +#[derive(strum::EnumIter, strum::Display, Debug)] +pub(crate) enum Rating { + #[strum(serialize = "u")] + Unrated, + #[strum(serialize = "s")] + Safe, + #[strum(serialize = "q")] + Questionable, + #[strum(serialize = "e")] + Explicit, +} + +#[derive(strum::EnumIter, strum::Display, Debug)] +pub(crate) enum MediaType { + #[strum(serialize = "image")] + Image, + #[strum(serialize = "video")] + Video, +} diff --git a/src/views.rs b/src/views.rs index 2545502..6f574c4 100644 --- a/src/views.rs +++ b/src/views.rs @@ -24,10 +24,11 @@ use sea_orm::{ ModelTrait, PaginatorTrait, QueryFilter, QuerySelect, }; use serde::Deserialize; +use strum::IntoEnumIterator; use tokio::{task::spawn_blocking, try_join}; use crate::{ - AppState, NEGATIVE_PREFIX, RATING_PREFIX, + AppState, auth::{AuthSession, Credentials, User}, config::{AGE_CONFIRMATION_KEY, APPLICATION_NAME_KEY, BASE_URL_KEY}, entities::{ @@ -43,6 +44,7 @@ use crate::{ PoolPost, PostOverview, PostPoolData, clean_dangling_tags, filter_posts_by_user, get_pool_data_for_post, get_posts_in_pool, get_tags_for_post, search_posts, }, + tags::{MEDIA_TYPE_PREFIX, MediaType, NEGATIVE_PREFIX, RATING_PREFIX, Rating}, video::{generate_thumbnail, get_dimensions_for_video}, }; @@ -531,19 +533,23 @@ pub(crate) async fn search_tags( vec![] } else if let Some(stripped_tag) = tag.strip_prefix(NEGATIVE_PREFIX) { if stripped_tag.starts_with(RATING_PREFIX) { - [ - format!("{}u", RATING_PREFIX), - format!("{}s", RATING_PREFIX), - format!("{}q", RATING_PREFIX), - format!("{}e", RATING_PREFIX), - ] - .into_iter() - .filter(|t| t.starts_with(stripped_tag)) - .map(|tag| SearchTag { - value: format!("-{}", &tag), - name: tag, - }) - .collect() + Rating::iter() + .map(|rating| format!("{}{}", RATING_PREFIX, rating)) + .filter(|t| t.starts_with(stripped_tag)) + .map(|tag| SearchTag { + value: format!("-{}", &tag), + name: tag, + }) + .collect() + } else if stripped_tag.starts_with(MEDIA_TYPE_PREFIX) { + MediaType::iter() + .map(|rating| format!("{}{}", MEDIA_TYPE_PREFIX, rating)) + .filter(|t| t.starts_with(stripped_tag)) + .map(|tag| SearchTag { + value: format!("-{}", &tag), + name: tag, + }) + .collect() } else { SameyTag::find() .filter(Expr::cust_with_expr( @@ -561,19 +567,23 @@ pub(crate) async fn search_tags( .collect() } } else if tag.starts_with(RATING_PREFIX) { - [ - format!("{}u", RATING_PREFIX), - format!("{}s", RATING_PREFIX), - format!("{}q", RATING_PREFIX), - format!("{}e", RATING_PREFIX), - ] - .into_iter() - .filter(|t| t.starts_with(tag)) - .map(|tag| SearchTag { - value: tag.clone(), - name: tag, - }) - .collect() + Rating::iter() + .map(|rating| format!("{}{}", RATING_PREFIX, rating)) + .filter(|t| t.starts_with(tag)) + .map(|tag| SearchTag { + value: tag.clone(), + name: tag, + }) + .collect() + } else if tag.starts_with(MEDIA_TYPE_PREFIX) { + MediaType::iter() + .map(|rating| format!("{}{}", MEDIA_TYPE_PREFIX, rating)) + .filter(|t| t.starts_with(tag)) + .map(|tag| SearchTag { + value: tag.clone(), + name: tag, + }) + .collect() } else { SameyTag::find() .filter(Expr::cust_with_expr(