Filters
This commit is contained in:
parent
a5e3fb2850
commit
04f888c323
6 changed files with 182 additions and 69 deletions
|
|
@ -4,7 +4,6 @@ Sam's small image board. Currently a WIP.
|
|||
|
||||
## TODO
|
||||
|
||||
- [ ] Improve filters
|
||||
- [ ] Pools
|
||||
- [ ] Video support
|
||||
- [ ] CSS
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ use crate::views::{
|
|||
submit_post_details, upload, view_post,
|
||||
};
|
||||
|
||||
pub(crate) const NEGATIVE_PREFIX: &str = "-";
|
||||
pub(crate) const RATING_PREFIX: &str = "rating:";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AppState {
|
||||
files_dir: Arc<String>,
|
||||
|
|
|
|||
108
src/query.rs
108
src/query.rs
|
|
@ -7,6 +7,7 @@ use sea_orm::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
NEGATIVE_PREFIX, RATING_PREFIX,
|
||||
auth::User,
|
||||
entities::{
|
||||
prelude::{SameyPost, SameyTag, SameyTagPost},
|
||||
|
|
@ -25,13 +26,29 @@ pub(crate) fn search_posts(
|
|||
tags: Option<&Vec<&str>>,
|
||||
user: Option<User>,
|
||||
) -> Selector<SelectModel<SearchPost>> {
|
||||
let tags: HashSet<String> = match tags {
|
||||
Some(tags) if !tags.is_empty() => tags.iter().map(|&tag| tag.to_lowercase()).collect(),
|
||||
_ => HashSet::new(),
|
||||
};
|
||||
let mut include_tags = HashSet::<String>::new();
|
||||
let mut exclude_tags = HashSet::<String>::new();
|
||||
let mut include_ratings = HashSet::<String>::new();
|
||||
let mut exclude_ratings = HashSet::<String>::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()));
|
||||
} else {
|
||||
exclude_tags.insert(tag.split_off(NEGATIVE_PREFIX.len()));
|
||||
}
|
||||
} else if tag.starts_with(RATING_PREFIX) {
|
||||
include_ratings.insert(tag.split_off(RATING_PREFIX.len()));
|
||||
} else {
|
||||
include_tags.insert(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut query = if tags.is_empty() {
|
||||
SameyPost::find()
|
||||
let mut query = if include_tags.is_empty() && exclude_tags.is_empty() {
|
||||
let mut query = SameyPost::find()
|
||||
.select_only()
|
||||
.column(samey_post::Column::Id)
|
||||
.column(samey_post::Column::Thumbnail)
|
||||
|
|
@ -43,12 +60,31 @@ pub(crate) fn search_posts(
|
|||
.join(
|
||||
sea_orm::JoinType::InnerJoin,
|
||||
samey_tag_post::Relation::SameyTag.def(),
|
||||
)
|
||||
.group_by(samey_post::Column::Id)
|
||||
.order_by_desc(samey_post::Column::Id)
|
||||
);
|
||||
if !include_ratings.is_empty() {
|
||||
query = query.filter(samey_post::Column::Rating.is_in(include_ratings))
|
||||
}
|
||||
if !exclude_ratings.is_empty() {
|
||||
query = query.filter(samey_post::Column::Rating.is_not_in(exclude_ratings))
|
||||
}
|
||||
query
|
||||
} else {
|
||||
let tags_count = tags.len() as u32;
|
||||
let subquery = Query::select()
|
||||
let mut query = SameyPost::find()
|
||||
.select_only()
|
||||
.column(samey_post::Column::Id)
|
||||
.column(samey_post::Column::Thumbnail)
|
||||
.column_as(
|
||||
Expr::cust("GROUP_CONCAT(\"samey_tag\".\"name\", ' ')"),
|
||||
"tags",
|
||||
)
|
||||
.inner_join(SameyTagPost)
|
||||
.join(
|
||||
sea_orm::JoinType::InnerJoin,
|
||||
samey_tag_post::Relation::SameyTag.def(),
|
||||
);
|
||||
if !include_tags.is_empty() {
|
||||
let include_tags_count = include_tags.len() as u32;
|
||||
let include_tags_subquery = Query::select()
|
||||
.column((SameyPost, samey_post::Column::Id))
|
||||
.from(SameyPost)
|
||||
.inner_join(
|
||||
|
|
@ -61,28 +97,39 @@ pub(crate) fn search_posts(
|
|||
Expr::col((SameyTagPost, samey_tag_post::Column::TagId))
|
||||
.equals((SameyTag, samey_tag::Column::Id)),
|
||||
)
|
||||
.and_where(samey_tag::Column::NormalizedName.is_in(tags))
|
||||
.and_where(samey_tag::Column::NormalizedName.is_in(include_tags))
|
||||
.group_by_col((SameyPost, samey_post::Column::Id))
|
||||
.and_having(samey_tag::Column::Id.count().eq(tags_count))
|
||||
.and_having(samey_tag::Column::Id.count().eq(include_tags_count))
|
||||
.to_owned();
|
||||
SameyPost::find()
|
||||
.select_only()
|
||||
.column(samey_post::Column::Id)
|
||||
.column(samey_post::Column::Thumbnail)
|
||||
.column_as(
|
||||
Expr::cust("GROUP_CONCAT(\"samey_tag\".\"name\", ' ')"),
|
||||
"tags",
|
||||
query = query.filter(samey_post::Column::Id.in_subquery(include_tags_subquery));
|
||||
}
|
||||
if !exclude_tags.is_empty() {
|
||||
let exclude_tags_subquery = Query::select()
|
||||
.column((SameyPost, samey_post::Column::Id))
|
||||
.from(SameyPost)
|
||||
.inner_join(
|
||||
SameyTagPost,
|
||||
Expr::col((SameyPost, samey_post::Column::Id))
|
||||
.equals((SameyTagPost, samey_tag_post::Column::PostId)),
|
||||
)
|
||||
.inner_join(SameyTagPost)
|
||||
.join(
|
||||
sea_orm::JoinType::InnerJoin,
|
||||
samey_tag_post::Relation::SameyTag.def(),
|
||||
.inner_join(
|
||||
SameyTag,
|
||||
Expr::col((SameyTagPost, samey_tag_post::Column::TagId))
|
||||
.equals((SameyTag, samey_tag::Column::Id)),
|
||||
)
|
||||
.filter(samey_post::Column::Id.in_subquery(subquery))
|
||||
.group_by(samey_post::Column::Id)
|
||||
.order_by_desc(samey_post::Column::Id)
|
||||
// println!("{}", &query.build(sea_orm::DatabaseBackend::Sqlite).sql);
|
||||
.and_where(samey_tag::Column::NormalizedName.is_in(exclude_tags))
|
||||
.to_owned();
|
||||
query = query.filter(samey_post::Column::Id.not_in_subquery(exclude_tags_subquery));
|
||||
}
|
||||
if !include_ratings.is_empty() {
|
||||
query = query.filter(samey_post::Column::Rating.is_in(include_ratings))
|
||||
}
|
||||
if !exclude_ratings.is_empty() {
|
||||
query = query.filter(samey_post::Column::Rating.is_not_in(exclude_ratings))
|
||||
}
|
||||
query
|
||||
};
|
||||
|
||||
query = match user {
|
||||
None => query.filter(samey_post::Column::IsPublic.into_simple_expr()),
|
||||
Some(user) if !user.is_admin => query.filter(
|
||||
|
|
@ -93,7 +140,10 @@ pub(crate) fn search_posts(
|
|||
_ => query,
|
||||
};
|
||||
|
||||
query.into_model::<SearchPost>()
|
||||
query
|
||||
.group_by(samey_post::Column::Id)
|
||||
.order_by_desc(samey_post::Column::Id)
|
||||
.into_model::<SearchPost>()
|
||||
}
|
||||
|
||||
pub(crate) fn get_tags_for_post(post_id: i32) -> Select<SameyTag> {
|
||||
|
|
|
|||
63
src/views.rs
63
src/views.rs
|
|
@ -23,7 +23,7 @@ use serde::Deserialize;
|
|||
use tokio::task::spawn_blocking;
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
AppState, NEGATIVE_PREFIX, RATING_PREFIX,
|
||||
auth::{AuthSession, Credentials, User},
|
||||
entities::{
|
||||
prelude::{SameyPost, SameyPostSource, SameyTag, SameyTagPost},
|
||||
|
|
@ -226,10 +226,15 @@ pub(crate) async fn upload(
|
|||
|
||||
// Search fields views
|
||||
|
||||
struct SearchTag {
|
||||
name: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "search_tags.html")]
|
||||
struct SearchTagsTemplate {
|
||||
tags: Vec<samey_tag::Model>,
|
||||
tags: Vec<SearchTag>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
@ -242,7 +247,52 @@ pub(crate) async fn search_tags(
|
|||
Form(body): Form<SearchTagsForm>,
|
||||
) -> Result<impl IntoResponse, SameyError> {
|
||||
let tags = match body.tags.split(' ').last() {
|
||||
Some(tag) if !tag.is_empty() => {
|
||||
Some(tag) => {
|
||||
if tag.starts_with(NEGATIVE_PREFIX) {
|
||||
if tag[NEGATIVE_PREFIX.len()..].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[NEGATIVE_PREFIX.len()..]))
|
||||
.map(|tag| SearchTag {
|
||||
value: format!("-{}", &tag),
|
||||
name: tag,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
SameyTag::find()
|
||||
.filter(Expr::cust_with_expr(
|
||||
"LOWER(\"samey_tag\".\"name\") LIKE CONCAT(?, '%')",
|
||||
tag[NEGATIVE_PREFIX.len()..].to_lowercase(),
|
||||
))
|
||||
.all(&db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|tag| SearchTag {
|
||||
value: format!("-{}", &tag.name),
|
||||
name: tag.name,
|
||||
})
|
||||
.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()
|
||||
} else {
|
||||
SameyTag::find()
|
||||
.filter(Expr::cust_with_expr(
|
||||
"LOWER(\"samey_tag\".\"name\") LIKE CONCAT(?, '%')",
|
||||
|
|
@ -250,6 +300,13 @@ pub(crate) async fn search_tags(
|
|||
))
|
||||
.all(&db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|tag| SearchTag {
|
||||
value: tag.name.clone(),
|
||||
name: tag.name,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@
|
|||
</article>
|
||||
<main>
|
||||
<h1>Viewing posts</h1>
|
||||
{% if posts.is_empty() %}
|
||||
<div>No posts found!</div>
|
||||
{% else %}
|
||||
<ul>
|
||||
{% for post in posts %}
|
||||
<li>
|
||||
|
|
@ -48,6 +51,7 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
<div>Page {{ page }} of {{ page_count }}</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% for tag in tags %}
|
||||
<li>
|
||||
<button
|
||||
hx-post="/select_tag/{{ tag.name }}"
|
||||
hx-post="/select_tag/{{ tag.value }}"
|
||||
hx-target="previous .tags"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue