Refactor autocompletion routes.

This commit is contained in:
Rasmus Kaj 2022-07-16 22:20:01 +02:00
parent 86d301ada5
commit 2660c91732

View File

@ -1,6 +1,8 @@
use super::{wrap, Context, Result};
use crate::schema::people::dsl as h; // h as in human
use crate::schema::photo_people::dsl as pp;
use crate::schema::photo_places::dsl as lp;
use crate::schema::photo_tags::dsl as tp;
use crate::schema::photos::dsl as p;
use crate::schema::places::dsl as l;
use crate::schema::tags::dsl as t;
@ -17,127 +19,42 @@ use warp::reply::{json, Json, Response};
use warp::Filter;
pub fn routes(s: BoxedFilter<(Context,)>) -> BoxedFilter<(Response,)> {
end()
.and(get())
.and(s.clone())
.and(query())
.map(auto_complete_any)
.or(path("tag")
.and(get())
.and(s.clone())
.and(query())
.map(auto_complete_tag))
.unify()
.or(path("person")
.and(get())
.and(s)
.and(query())
.map(auto_complete_person))
.unify()
.map(wrap)
.boxed()
let egs = end().and(get()).and(s);
let any = egs.clone().and(query()).map(list_any);
let tag = path("tag").and(egs.clone()).and(query()).map(list_tags);
let person = path("person").and(egs).and(query()).map(list_people);
any.or(tag).unify().or(person).unify().map(wrap).boxed()
}
sql_function!(fn lower(string: Text) -> Text);
sql_function!(fn strpos(string: Text, substring: Text) -> Integer);
fn auto_complete_any(context: Context, term: AcQ) -> Result<Json> {
let tpos = strpos(lower(t::tag_name), &term.q);
let query = t::tags
.select((t::tag_name, t::slug, tpos))
.filter(tpos.gt(0))
.into_boxed();
let query = if context.is_authorized() {
query
} else {
use crate::schema::photo_tags::dsl as tp;
query.filter(t::id.eq_any(tp::photo_tags.select(tp::tag_id).filter(
tp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)),
)))
};
let db = context.db()?;
let mut tags = query
.order((tpos, t::tag_name))
.limit(10)
.load::<(String, String, i32)>(&db)?
fn list_any(context: Context, term: AcQ) -> Result<Json> {
let mut tags = select_tags(&context, &term)?
.into_iter()
.map(|(t, s, p)| (SearchTag { k: 't', t, s }, p))
.chain({
select_people(&context, &term)?
.into_iter()
.map(|(t, s, p)| (SearchTag { k: 'p', t, s }, p))
})
.chain({
select_places(&context, &term)?
.into_iter()
.map(|(t, s, p)| (SearchTag { k: 'l', t, s }, p))
})
.collect::<Vec<_>>();
tags.extend({
let ppos = strpos(lower(h::person_name), &term.q);
let query = h::people
.select((h::person_name, h::slug, ppos))
.filter(ppos.gt(0))
.into_boxed();
let query =
if context.is_authorized() {
query
} else {
query.filter(h::id.eq_any(
pp::photo_people.select(pp::person_id).filter(
pp::photo_id.eq_any(
p::photos.select(p::id).filter(p::is_public),
),
),
))
};
query
.order((ppos, h::person_name))
.limit(10)
.load::<(String, String, i32)>(&db)?
.into_iter()
.map(|(t, s, p)| (SearchTag { k: 'p', t, s }, p))
});
tags.extend({
let lpos = strpos(lower(l::place_name), &term.q);
let query = l::places
.select((l::place_name, l::slug, lpos))
.filter(lpos.gt(0))
.into_boxed();
let query =
if context.is_authorized() {
query
} else {
use crate::schema::photo_places::dsl as lp;
query.filter(l::id.eq_any(
lp::photo_places.select(lp::place_id).filter(
lp::photo_id.eq_any(
p::photos.select(p::id).filter(p::is_public),
),
),
))
};
query
.order((lpos, l::place_name))
.limit(10)
.load::<(String, String, i32)>(&db)?
.into_iter()
.map(|(t, s, p)| (SearchTag { k: 'l', t, s }, p))
});
tags.sort_by(|a, b| a.1.cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
Ok(json(&tags.iter().map(|(t, _)| t).collect::<Vec<_>>()))
}
fn auto_complete_tag(context: Context, query: AcQ) -> Result<Json> {
use crate::schema::tags::dsl::{tag_name, tags};
let tpos = strpos(lower(tag_name), query.q);
let q = tags
.select(tag_name)
.filter((&tpos).gt(0))
.order((&tpos, tag_name))
.limit(10);
Ok(json(&q.load::<String>(&context.db()?)?))
fn list_tags(context: Context, query: AcQ) -> Result<Json> {
Ok(json(&names(select_tags(&context, &query)?)))
}
fn auto_complete_person(context: Context, query: AcQ) -> Result<Json> {
use crate::schema::people::dsl::{people, person_name};
let mpos = strpos(lower(person_name), query.q);
let q = people
.select(person_name)
.filter((&mpos).gt(0))
.order((&mpos, person_name))
.limit(10);
Ok(json(&q.load::<String>(&context.db()?)?))
fn list_people(context: Context, query: AcQ) -> Result<Json> {
Ok(json(&names(select_people(&context, &query)?)))
}
fn names(data: Vec<NameSlugScore>) -> Vec<String> {
data.into_iter().map(|(name, _, _)| name).collect()
}
/// A `q` query argument which must not contain the null character.
@ -224,3 +141,69 @@ mod tests {
);
}
}
sql_function!(fn lower(string: Text) -> Text);
sql_function!(fn strpos(string: Text, substring: Text) -> Integer);
type NameSlugScore = (String, String, i32);
fn select_tags(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
let tpos = strpos(lower(t::tag_name), &term.q);
let query = t::tags
.select((t::tag_name, t::slug, tpos))
.filter(tpos.gt(0))
.into_boxed();
let query = if context.is_authorized() {
query
} else {
query.filter(t::id.eq_any(tp::photo_tags.select(tp::tag_id).filter(
tp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)),
)))
};
let db = context.db()?;
Ok(query.order((tpos, t::tag_name)).limit(10).load(&db)?)
}
fn select_people(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
let ppos = strpos(lower(h::person_name), &term.q);
let query = h::people
.select((h::person_name, h::slug, ppos))
.filter(ppos.gt(0))
.into_boxed();
let query = if context.is_authorized() {
query
} else {
query.filter(
h::id.eq_any(
pp::photo_people.select(pp::person_id).filter(
pp::photo_id
.eq_any(p::photos.select(p::id).filter(p::is_public)),
),
),
)
};
let db = context.db()?;
Ok(query.order((ppos, h::person_name)).limit(10).load(&db)?)
}
fn select_places(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
let lpos = strpos(lower(l::place_name), &term.q);
let query = l::places
.select((l::place_name, l::slug, lpos))
.filter(lpos.gt(0))
.into_boxed();
let query = if context.is_authorized() {
query
} else {
query.filter(
l::id.eq_any(
lp::photo_places.select(lp::place_id).filter(
lp::photo_id
.eq_any(p::photos.select(p::id).filter(p::is_public)),
),
),
)
};
let db = context.db()?;
Ok(query.order((lpos, l::place_name)).limit(10).load(&db)?)
}