update how to search

This commit is contained in:
polv committed 2021-10-21 08:32:27 +07:00
1 parent 9090d4e2ca
commit 1e9bb49e29
6 files changed
+506 -63

No files matched your search

+155
View File
@@ -0,0 +1,155 @@
import { sql } from '@databases/pg'
import { FastifyPluginAsync } from 'fastify'
import S from 'jsonschema-definer'
import { db } from '../shared'
import { QSplit, qParseNum, pgOp } from '../util/search'
export const searchRouter: FastifyPluginAsync = async (f) => {
{
const sQuery = S.shape({
q: S.string().minLength(1),
offset: S.integer().optional(),
limit: S.integer().optional(),
})
const sResponse = S.shape({
result: S.list(
S.shape({
entry: S.string(),
type: S.string(),
}).additionalProperties(true)
),
offset: S.integer(),
limit: S.integer(),
count: S.integer(),
})
const makeEntry = new QSplit({
default(v) {
if (v.includes('*')) {
return sql`"entry" && (
SELECT array_agg("entry") FROM "frequency" WHERE "entry" LIKE ${'%' + v.replace('*', '%') + '%'}
)`
}
if (/[\p{sc=Han}\p{sc=Katakana}\p{sc=Hiragana}]/u.test(v)) {
return sql`(${sql.join(
[
this.fields['entry']![':']!(v),
this.fields['reading']![':']!(v),
this.fields['translation']![':']!(v),
],
' OR '
)})`
}
if (/[^\p{L}\p{N}\p{M} :-]/u.test(v)) {
return null
}
return sql`(${sql.join(
[
this.fields['reading']![':']!(v),
this.fields['translation']![':']!(v),
],
' OR '
)})`
},
fields: {
entry: { ':': (v) => sql`"entry" &@ ${v}` },
kana: { ':': (v) => sql`"reading" &@ ${v}` },
reading: { ':': (v) => sql`"reading" &@ ${v}` },
kunyomi: {
':': (v) => sql`"reading" ${pgOp} ${`paths @ ".kunyomi" && string @ ${JSON.stringify(v)}`}`,
},
onyomi: {
':': (v) => sql`"reading" ${pgOp} ${`paths @ ".onyomi" && string @ ${JSON.stringify(v)}`}`,
},
nanori: {
':': (v) => sql`"reading" ${pgOp} ${`paths @ ".nanori" && string @ ${JSON.stringify(v)}`}`,
},
english: {
':': (v) => sql`"translation" ${pgOp} ${`paths @ ".en" && string @ ${JSON.stringify(v)}`}`,
},
translation: { ':': (v) => sql`"translation" &@ ${v}` },
type: {
':': (v) => sql`"_type" @> ARRAY[${v.toLocaleLowerCase()}]`,
},
},
})
const makeLevel = new QSplit({
default: () => null,
fields: {
level: qParseNum(sql`("wanikani.subjects"."data" -> 'level')::int`),
},
})
f.get<{
Querystring: typeof sQuery.type
}>(
'/',
{
schema: {
querystring: sQuery.valueOf(),
response: {
200: sResponse.valueOf(),
},
},
},
async (req): Promise<typeof sResponse.type> => {
const { q, offset = 0, limit = 5 } = req.query
const entryCond = makeEntry.parse(q)
const levelCond = makeLevel.parse(q)
const [r] = await db.query(sql`
WITH cte AS (
SELECT DISTINCT ON (unnest, "type")
unnest "entry", "type", "priority", "frequency"
FROM (
SELECT
unnest("entry"), *
FROM "entry"
${
levelCond
? sql`JOIN "wanikani.subjects" ON
"entry"."_type" @> ARRAY["wanikani.subjects"."object"]
AND "entry"."entry" @> ARRAY["wanikani.subjects"."data" ->> 'characters']`
: sql``
}
WHERE
${entryCond || sql`TRUE`}
AND ${levelCond || sql`TRUE`}
) t1
JOIN "frequency" ON "frequency"."entry" = unnest
)
SELECT
(SELECT array_agg(jsonb_build_object(
'entry', "entry",
'type', "type",
'frequency', "frequency"
)) FROM (
SELECT * FROM cte
ORDER BY "priority" DESC, "frequency" DESC
LIMIT ${limit} OFFSET ${offset}
) t1)||'{}'::jsonb[] "result",
${offset} "offset",
${limit} "limit",
(SELECT count(*) FROM cte) "count"
`)
return (
r || {
result: [],
offset,
limit,
count: 0,
}
)
}
)
}
}