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
|
## TODO
|
||||||
|
|
||||||
- [ ] Improve filters
|
|
||||||
- [ ] Pools
|
- [ ] Pools
|
||||||
- [ ] Video support
|
- [ ] Video support
|
||||||
- [ ] CSS
|
- [ ] CSS
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ use crate::views::{
|
||||||
submit_post_details, upload, view_post,
|
submit_post_details, upload, view_post,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub(crate) const NEGATIVE_PREFIX: &str = "-";
|
||||||
|
pub(crate) const RATING_PREFIX: &str = "rating:";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct AppState {
|
pub(crate) struct AppState {
|
||||||
files_dir: Arc<String>,
|
files_dir: Arc<String>,
|
||||||
|
|
|
||||||
164
src/query.rs
164
src/query.rs
|
|
@ -7,6 +7,7 @@ use sea_orm::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
NEGATIVE_PREFIX, RATING_PREFIX,
|
||||||
auth::User,
|
auth::User,
|
||||||
entities::{
|
entities::{
|
||||||
prelude::{SameyPost, SameyTag, SameyTagPost},
|
prelude::{SameyPost, SameyTag, SameyTagPost},
|
||||||
|
|
@ -25,64 +26,110 @@ pub(crate) fn search_posts(
|
||||||
tags: Option<&Vec<&str>>,
|
tags: Option<&Vec<&str>>,
|
||||||
user: Option<User>,
|
user: Option<User>,
|
||||||
) -> Selector<SelectModel<SearchPost>> {
|
) -> Selector<SelectModel<SearchPost>> {
|
||||||
let tags: HashSet<String> = match tags {
|
let mut include_tags = HashSet::<String>::new();
|
||||||
Some(tags) if !tags.is_empty() => tags.iter().map(|&tag| tag.to_lowercase()).collect(),
|
let mut exclude_tags = HashSet::<String>::new();
|
||||||
_ => HashSet::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 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)
|
||||||
|
.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_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 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(
|
||||||
|
SameyTagPost,
|
||||||
|
Expr::col((SameyPost, samey_post::Column::Id))
|
||||||
|
.equals((SameyTagPost, samey_tag_post::Column::PostId)),
|
||||||
|
)
|
||||||
|
.inner_join(
|
||||||
|
SameyTag,
|
||||||
|
Expr::col((SameyTagPost, samey_tag_post::Column::TagId))
|
||||||
|
.equals((SameyTag, samey_tag::Column::Id)),
|
||||||
|
)
|
||||||
|
.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(include_tags_count))
|
||||||
|
.to_owned();
|
||||||
|
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(
|
||||||
|
SameyTag,
|
||||||
|
Expr::col((SameyTagPost, samey_tag_post::Column::TagId))
|
||||||
|
.equals((SameyTag, samey_tag::Column::Id)),
|
||||||
|
)
|
||||||
|
.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
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut query = if tags.is_empty() {
|
|
||||||
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(),
|
|
||||||
)
|
|
||||||
.group_by(samey_post::Column::Id)
|
|
||||||
.order_by_desc(samey_post::Column::Id)
|
|
||||||
} else {
|
|
||||||
let tags_count = tags.len() as u32;
|
|
||||||
let 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(
|
|
||||||
SameyTag,
|
|
||||||
Expr::col((SameyTagPost, samey_tag_post::Column::TagId))
|
|
||||||
.equals((SameyTag, samey_tag::Column::Id)),
|
|
||||||
)
|
|
||||||
.and_where(samey_tag::Column::NormalizedName.is_in(tags))
|
|
||||||
.group_by_col((SameyPost, samey_post::Column::Id))
|
|
||||||
.and_having(samey_tag::Column::Id.count().eq(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",
|
|
||||||
)
|
|
||||||
.inner_join(SameyTagPost)
|
|
||||||
.join(
|
|
||||||
sea_orm::JoinType::InnerJoin,
|
|
||||||
samey_tag_post::Relation::SameyTag.def(),
|
|
||||||
)
|
|
||||||
.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);
|
|
||||||
};
|
|
||||||
query = match user {
|
query = match user {
|
||||||
None => query.filter(samey_post::Column::IsPublic.into_simple_expr()),
|
None => query.filter(samey_post::Column::IsPublic.into_simple_expr()),
|
||||||
Some(user) if !user.is_admin => query.filter(
|
Some(user) if !user.is_admin => query.filter(
|
||||||
|
|
@ -93,7 +140,10 @@ pub(crate) fn search_posts(
|
||||||
_ => query,
|
_ => 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> {
|
pub(crate) fn get_tags_for_post(post_id: i32) -> Select<SameyTag> {
|
||||||
|
|
|
||||||
77
src/views.rs
77
src/views.rs
|
|
@ -23,7 +23,7 @@ use serde::Deserialize;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState, NEGATIVE_PREFIX, RATING_PREFIX,
|
||||||
auth::{AuthSession, Credentials, User},
|
auth::{AuthSession, Credentials, User},
|
||||||
entities::{
|
entities::{
|
||||||
prelude::{SameyPost, SameyPostSource, SameyTag, SameyTagPost},
|
prelude::{SameyPost, SameyPostSource, SameyTag, SameyTagPost},
|
||||||
|
|
@ -226,10 +226,15 @@ pub(crate) async fn upload(
|
||||||
|
|
||||||
// Search fields views
|
// Search fields views
|
||||||
|
|
||||||
|
struct SearchTag {
|
||||||
|
name: String,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "search_tags.html")]
|
#[template(path = "search_tags.html")]
|
||||||
struct SearchTagsTemplate {
|
struct SearchTagsTemplate {
|
||||||
tags: Vec<samey_tag::Model>,
|
tags: Vec<SearchTag>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
@ -242,14 +247,66 @@ pub(crate) async fn search_tags(
|
||||||
Form(body): Form<SearchTagsForm>,
|
Form(body): Form<SearchTagsForm>,
|
||||||
) -> Result<impl IntoResponse, SameyError> {
|
) -> Result<impl IntoResponse, SameyError> {
|
||||||
let tags = match body.tags.split(' ').last() {
|
let tags = match body.tags.split(' ').last() {
|
||||||
Some(tag) if !tag.is_empty() => {
|
Some(tag) => {
|
||||||
SameyTag::find()
|
if tag.starts_with(NEGATIVE_PREFIX) {
|
||||||
.filter(Expr::cust_with_expr(
|
if tag[NEGATIVE_PREFIX.len()..].starts_with(RATING_PREFIX) {
|
||||||
"LOWER(\"samey_tag\".\"name\") LIKE CONCAT(?, '%')",
|
[
|
||||||
tag.to_lowercase(),
|
format!("{}u", RATING_PREFIX),
|
||||||
))
|
format!("{}s", RATING_PREFIX),
|
||||||
.all(&db)
|
format!("{}q", RATING_PREFIX),
|
||||||
.await?
|
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(?, '%')",
|
||||||
|
tag.to_lowercase(),
|
||||||
|
))
|
||||||
|
.all(&db)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|tag| SearchTag {
|
||||||
|
value: tag.name.clone(),
|
||||||
|
name: tag.name,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@
|
||||||
</article>
|
</article>
|
||||||
<main>
|
<main>
|
||||||
<h1>Viewing posts</h1>
|
<h1>Viewing posts</h1>
|
||||||
|
{% if posts.is_empty() %}
|
||||||
|
<div>No posts found!</div>
|
||||||
|
{% else %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
<li>
|
<li>
|
||||||
|
|
@ -48,6 +51,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<div>Page {{ page }} of {{ page_count }}</div>
|
<div>Page {{ page }} of {{ page_count }}</div>
|
||||||
|
{% endif %}
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
hx-post="/select_tag/{{ tag.name }}"
|
hx-post="/select_tag/{{ tag.value }}"
|
||||||
hx-target="previous .tags"
|
hx-target="previous .tags"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue