Search by media type

This commit is contained in:
Bad Manners 2025-04-22 19:30:56 -03:00
parent e679d167fc
commit 8c9bdbb58e
8 changed files with 115 additions and 74 deletions

25
Cargo.lock generated
View file

@ -3048,6 +3048,7 @@ dependencies = [
"sea-orm", "sea-orm",
"serde", "serde",
"serde_json", "serde_json",
"strum 0.27.1",
"thiserror 2.0.12", "thiserror 2.0.12",
"time", "time",
"tokio", "tokio",
@ -3103,7 +3104,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sqlx", "sqlx",
"strum", "strum 0.26.3",
"thiserror 2.0.12", "thiserror 2.0.12",
"time", "time",
"tracing", "tracing",
@ -3678,6 +3679,28 @@ version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 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]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"

View file

@ -45,6 +45,7 @@ time = "0.3.41"
tokio = { version = "1.44.1", features = ["full"] } tokio = { version = "1.44.1", features = ["full"] }
tower-http = { version = "0.6.2", features = ["fs"] } tower-http = { version = "0.6.2", features = ["fs"] }
tower-sessions = "0.14.0" tower-sessions = "0.14.0"
strum = { version = "0.27.1", features = ["derive"] }
[profile.release] [profile.release]
strip = true strip = true

View file

@ -15,11 +15,10 @@ Still very much an early WIP.
### Possible roadmap ### Possible roadmap
- [ ] Caching - [ ] Display thumbnails on post selection
- [ ] Text media - [ ] Text media
- [ ] Improve CSS - [ ] Improve CSS
- [ ] User management - [ ] User management
- [ ] Display thumbnails on post selection
- [ ] Testing - [ ] Testing
- [ ] Lossless compression - [ ] Lossless compression
- [ ] Migrate to Cot...? - [ ] Migrate to Cot...?

View file

@ -5,7 +5,7 @@ pub(crate) mod config;
pub(crate) mod entities; pub(crate) mod entities;
pub(crate) mod error; pub(crate) mod error;
pub(crate) mod query; pub(crate) mod query;
pub(crate) mod rating; pub(crate) mod tags;
pub(crate) mod video; pub(crate) mod video;
pub(crate) mod views; pub(crate) mod views;
@ -35,9 +35,6 @@ use crate::entities::{prelude::SameyUser, samey_user};
pub use crate::error::SameyError; pub use crate::error::SameyError;
use crate::views::*; use crate::views::*;
pub(crate) const NEGATIVE_PREFIX: &str = "-";
pub(crate) const RATING_PREFIX: &str = "rating:";
#[derive(rust_embed::Embed)] #[derive(rust_embed::Embed)]
#[folder = "static/"] #[folder = "static/"]
struct Asset; struct Asset;

View file

@ -9,12 +9,13 @@ use sea_orm::{
}; };
use crate::{ use crate::{
NEGATIVE_PREFIX, RATING_PREFIX, SameyError, SameyError,
auth::User, auth::User,
entities::{ entities::{
prelude::{SameyPool, SameyPoolPost, SameyPost, SameyTag, SameyTagPost}, prelude::{SameyPool, SameyPoolPost, SameyPost, SameyTag, SameyTagPost},
samey_pool, samey_pool_post, samey_post, samey_tag, samey_tag_post, samey_pool, samey_pool_post, samey_post, samey_tag, samey_tag_post,
}, },
tags::{MEDIA_TYPE_PREFIX, NEGATIVE_PREFIX, RATING_PREFIX},
}; };
#[derive(Debug, FromQueryResult)] #[derive(Debug, FromQueryResult)]
@ -38,17 +39,23 @@ pub(crate) fn search_posts(
let mut exclude_tags = HashSet::<String>::new(); let mut exclude_tags = HashSet::<String>::new();
let mut include_ratings = HashSet::<String>::new(); let mut include_ratings = HashSet::<String>::new();
let mut exclude_ratings = HashSet::<String>::new(); let mut exclude_ratings = HashSet::<String>::new();
let mut include_types = HashSet::<String>::new();
let mut exclude_types = HashSet::<String>::new();
if let Some(tags) = tags { if let Some(tags) = tags {
for mut tag in tags.iter().map(|tag| tag.to_lowercase()) { for tag in tags.iter().map(|tag| tag.to_lowercase()) {
if tag.starts_with(NEGATIVE_PREFIX) { if let Some(negative_tag) = tag.strip_prefix(NEGATIVE_PREFIX) {
if tag.as_str()[NEGATIVE_PREFIX.len()..].starts_with(RATING_PREFIX) { if let Some(negative_rating_tag) = negative_tag.strip_prefix(RATING_PREFIX) {
exclude_ratings exclude_ratings.insert(negative_rating_tag.into());
.insert(tag.split_off(NEGATIVE_PREFIX.len() + RATING_PREFIX.len())); } else if let Some(negative_type_tag) = negative_tag.strip_prefix(MEDIA_TYPE_PREFIX)
{
exclude_types.insert(negative_type_tag.into());
} else { } else {
exclude_tags.insert(tag.split_off(NEGATIVE_PREFIX.len())); exclude_tags.insert(negative_tag.into());
} }
} else if tag.starts_with(RATING_PREFIX) { } else if let Some(rating_tag) = tag.strip_prefix(RATING_PREFIX) {
include_ratings.insert(tag.split_off(RATING_PREFIX.len())); 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 { } else {
include_tags.insert(tag); include_tags.insert(tag);
} }
@ -81,6 +88,12 @@ pub(crate) fn search_posts(
if !exclude_ratings.is_empty() { if !exclude_ratings.is_empty() {
query = query.filter(samey_post::Column::Rating.is_not_in(exclude_ratings)) 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 query
} else { } else {
let mut query = SameyPost::find() let mut query = SameyPost::find()
@ -147,6 +160,12 @@ pub(crate) fn search_posts(
if !exclude_ratings.is_empty() { if !exclude_ratings.is_empty() {
query = query.filter(samey_post::Column::Rating.is_not_in(exclude_ratings)) 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 query
}; };

View file

@ -1,31 +0,0 @@
use std::fmt::Display;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Rating {
Unrated,
Safe,
Questionable,
Explicit,
}
impl From<String> 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"),
}
}
}

23
src/tags.rs Normal file
View file

@ -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,
}

View file

@ -24,10 +24,11 @@ use sea_orm::{
ModelTrait, PaginatorTrait, QueryFilter, QuerySelect, ModelTrait, PaginatorTrait, QueryFilter, QuerySelect,
}; };
use serde::Deserialize; use serde::Deserialize;
use strum::IntoEnumIterator;
use tokio::{task::spawn_blocking, try_join}; use tokio::{task::spawn_blocking, try_join};
use crate::{ use crate::{
AppState, NEGATIVE_PREFIX, RATING_PREFIX, AppState,
auth::{AuthSession, Credentials, User}, auth::{AuthSession, Credentials, User},
config::{AGE_CONFIRMATION_KEY, APPLICATION_NAME_KEY, BASE_URL_KEY}, config::{AGE_CONFIRMATION_KEY, APPLICATION_NAME_KEY, BASE_URL_KEY},
entities::{ entities::{
@ -43,6 +44,7 @@ use crate::{
PoolPost, PostOverview, PostPoolData, clean_dangling_tags, filter_posts_by_user, 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, 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}, video::{generate_thumbnail, get_dimensions_for_video},
}; };
@ -531,13 +533,17 @@ pub(crate) async fn search_tags(
vec![] vec![]
} else if let Some(stripped_tag) = tag.strip_prefix(NEGATIVE_PREFIX) { } else if let Some(stripped_tag) = tag.strip_prefix(NEGATIVE_PREFIX) {
if stripped_tag.starts_with(RATING_PREFIX) { if stripped_tag.starts_with(RATING_PREFIX) {
[ Rating::iter()
format!("{}u", RATING_PREFIX), .map(|rating| format!("{}{}", RATING_PREFIX, rating))
format!("{}s", RATING_PREFIX), .filter(|t| t.starts_with(stripped_tag))
format!("{}q", RATING_PREFIX), .map(|tag| SearchTag {
format!("{}e", RATING_PREFIX), value: format!("-{}", &tag),
] name: tag,
.into_iter() })
.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)) .filter(|t| t.starts_with(stripped_tag))
.map(|tag| SearchTag { .map(|tag| SearchTag {
value: format!("-{}", &tag), value: format!("-{}", &tag),
@ -561,13 +567,17 @@ pub(crate) async fn search_tags(
.collect() .collect()
} }
} else if tag.starts_with(RATING_PREFIX) { } else if tag.starts_with(RATING_PREFIX) {
[ Rating::iter()
format!("{}u", RATING_PREFIX), .map(|rating| format!("{}{}", RATING_PREFIX, rating))
format!("{}s", RATING_PREFIX), .filter(|t| t.starts_with(tag))
format!("{}q", RATING_PREFIX), .map(|tag| SearchTag {
format!("{}e", RATING_PREFIX), value: tag.clone(),
] name: tag,
.into_iter() })
.collect()
} else if tag.starts_with(MEDIA_TYPE_PREFIX) {
MediaType::iter()
.map(|rating| format!("{}{}", MEDIA_TYPE_PREFIX, rating))
.filter(|t| t.starts_with(tag)) .filter(|t| t.starts_with(tag))
.map(|tag| SearchTag { .map(|tag| SearchTag {
value: tag.clone(), value: tag.clone(),