2022-10-28 16:00:53 +00:00
|
|
|
import { ITEM_FILTER_THRESHOLD } from '../../lib/constants'
|
2022-10-20 22:44:44 +00:00
|
|
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
|
|
|
import { getItem } from './item'
|
|
|
|
|
2022-10-26 22:46:01 +00:00
|
|
|
const STOP_WORDS = ['a', 'an', 'and', 'are', 'as', 'at', 'be', 'but',
|
|
|
|
'by', 'for', 'if', 'in', 'into', 'is', 'it', 'no', 'not',
|
|
|
|
'of', 'on', 'or', 'such', 'that', 'the', 'their', 'then',
|
|
|
|
'there', 'these', 'they', 'this', 'to', 'was', 'will',
|
|
|
|
'with', 'bitcoin', 'page', 'adds', 'how', 'why', 'what',
|
|
|
|
'works', 'now', 'available', 'breaking', 'app', 'powered',
|
|
|
|
'just', 'dev', 'using', 'crypto', 'has', 'my', 'i', 'apps',
|
|
|
|
'really', 'new', 'era', 'application', 'best', 'year',
|
|
|
|
'latest', 'still', 'few', 'crypto', 'keep', 'public', 'current',
|
|
|
|
'levels', 'from', 'cryptocurrencies', 'confirmed', 'news', 'network',
|
|
|
|
'about', 'sources', 'vote', 'considerations', 'hope',
|
|
|
|
'keep', 'keeps', 'including', 'we', 'brings', "don't", 'do',
|
2022-10-28 16:00:53 +00:00
|
|
|
'interesting', 'us', 'welcome']
|
2022-10-26 22:46:01 +00:00
|
|
|
|
2022-10-20 22:44:44 +00:00
|
|
|
export default {
|
|
|
|
Query: {
|
2023-01-12 20:30:17 +00:00
|
|
|
related: async (parent, { title, id, cursor, limit, minMatch }, { me, models, search }) => {
|
2022-10-26 22:46:01 +00:00
|
|
|
const decodedCursor = decodeCursor(cursor)
|
|
|
|
if (!title || title.trim().split(/\s+/).length < 1) {
|
|
|
|
if (id) {
|
|
|
|
const item = await getItem(parent, { id }, { me, models })
|
|
|
|
title = item?.title
|
|
|
|
}
|
|
|
|
if (!title) {
|
|
|
|
return {
|
|
|
|
items: [],
|
|
|
|
cursor: null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const mustNot = []
|
|
|
|
if (id) {
|
|
|
|
mustNot.push({ term: { id } })
|
|
|
|
}
|
|
|
|
|
|
|
|
let items = await search.search({
|
|
|
|
index: 'item',
|
|
|
|
size: limit || LIMIT,
|
|
|
|
from: decodedCursor.offset,
|
|
|
|
body: {
|
|
|
|
query: {
|
|
|
|
bool: {
|
2023-01-12 20:30:17 +00:00
|
|
|
must: [
|
2022-10-26 22:46:01 +00:00
|
|
|
{
|
|
|
|
more_like_this: {
|
|
|
|
fields: ['title'],
|
|
|
|
like: title,
|
|
|
|
min_term_freq: 1,
|
|
|
|
min_doc_freq: 1,
|
|
|
|
min_word_length: 2,
|
|
|
|
max_query_terms: 25,
|
2023-01-12 20:30:17 +00:00
|
|
|
minimum_should_match: minMatch || '20%',
|
2022-10-26 22:46:01 +00:00
|
|
|
stop_words: STOP_WORDS
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
must_not: [{ exists: { field: 'parentId' } }, ...mustNot],
|
|
|
|
filter: {
|
2023-01-12 20:30:17 +00:00
|
|
|
range: { wvotes: { gte: minMatch ? 0 : 0.2 } }
|
2022-10-26 22:46:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2022-10-28 16:00:53 +00:00
|
|
|
sort: ['_score', { wvotes: 'desc' }, { sats: 'desc' }]
|
2022-10-26 22:46:01 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
items = items.body.hits.hits.map(async e => {
|
|
|
|
// this is super inefficient but will suffice until we do something more generic
|
|
|
|
return await getItem(parent, { id: e._source.id }, { me, models })
|
|
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
|
|
cursor: items.length === (limit || LIMIT) ? nextCursorEncoded(decodedCursor) : null,
|
|
|
|
items
|
|
|
|
}
|
|
|
|
},
|
2023-07-23 15:08:43 +00:00
|
|
|
search: async (parent, { q: query, sub, cursor, sort, what, when }, { me, models, search }) => {
|
2022-10-20 22:44:44 +00:00
|
|
|
const decodedCursor = decodeCursor(cursor)
|
|
|
|
let sitems
|
|
|
|
|
2022-10-25 22:30:54 +00:00
|
|
|
if (!query) {
|
|
|
|
return {
|
|
|
|
items: [],
|
|
|
|
cursor: null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-24 21:32:16 +00:00
|
|
|
const whatArr = []
|
|
|
|
switch (what) {
|
|
|
|
case 'posts':
|
|
|
|
whatArr.push({ bool: { must_not: { exists: { field: 'parentId' } } } })
|
|
|
|
break
|
|
|
|
case 'comments':
|
|
|
|
whatArr.push({ bool: { must: { exists: { field: 'parentId' } } } })
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
const queryArr = query.trim().split(/\s+/)
|
|
|
|
const url = queryArr.find(word => word.startsWith('url:'))
|
|
|
|
const nym = queryArr.find(word => word.startsWith('nym:'))
|
2023-07-23 15:08:43 +00:00
|
|
|
const exclude = [url, nym]
|
2023-05-01 20:58:30 +00:00
|
|
|
query = queryArr.filter(word => !exclude.includes(word)).join(' ')
|
2022-10-24 21:32:16 +00:00
|
|
|
|
|
|
|
if (url) {
|
|
|
|
whatArr.push({ wildcard: { url: `*${url.slice(4).toLowerCase()}*` } })
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nym) {
|
|
|
|
whatArr.push({ wildcard: { 'user.name': `*${nym.slice(4).toLowerCase()}*` } })
|
|
|
|
}
|
|
|
|
|
2023-05-01 20:58:30 +00:00
|
|
|
if (sub) {
|
2023-07-23 15:08:43 +00:00
|
|
|
whatArr.push({ match: { 'sub.name': sub } })
|
2023-05-01 20:58:30 +00:00
|
|
|
}
|
|
|
|
|
2022-10-20 22:44:44 +00:00
|
|
|
const sortArr = []
|
|
|
|
switch (sort) {
|
|
|
|
case 'recent':
|
|
|
|
sortArr.push({ createdAt: 'desc' })
|
|
|
|
break
|
|
|
|
case 'comments':
|
|
|
|
sortArr.push({ ncomments: 'desc' })
|
|
|
|
break
|
|
|
|
case 'sats':
|
|
|
|
sortArr.push({ sats: 'desc' })
|
|
|
|
break
|
|
|
|
case 'votes':
|
2022-10-28 16:00:53 +00:00
|
|
|
sortArr.push({ wvotes: 'desc' })
|
2022-10-20 22:44:44 +00:00
|
|
|
break
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
sortArr.push('_score')
|
|
|
|
|
2022-10-24 21:32:16 +00:00
|
|
|
if (query.length) {
|
|
|
|
whatArr.push({
|
|
|
|
bool: {
|
|
|
|
should: [
|
|
|
|
{
|
|
|
|
// all terms are matched in fields
|
|
|
|
multi_match: {
|
|
|
|
query,
|
|
|
|
type: 'most_fields',
|
2022-10-28 16:00:53 +00:00
|
|
|
fields: ['title^100', 'text'],
|
2022-10-24 21:32:16 +00:00
|
|
|
minimum_should_match: '100%',
|
|
|
|
boost: 400
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// all terms are matched in fields
|
|
|
|
multi_match: {
|
|
|
|
query,
|
|
|
|
type: 'most_fields',
|
2022-10-28 16:00:53 +00:00
|
|
|
fields: ['title^100', 'text'],
|
2022-10-24 21:32:16 +00:00
|
|
|
fuzziness: 'AUTO',
|
|
|
|
prefix_length: 3,
|
|
|
|
minimum_should_match: '100%',
|
|
|
|
boost: 20
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// only some terms must match unless we're sorting
|
|
|
|
multi_match: {
|
|
|
|
query,
|
|
|
|
type: 'most_fields',
|
2022-10-28 16:00:53 +00:00
|
|
|
fields: ['title^100', 'text'],
|
2022-10-24 21:32:16 +00:00
|
|
|
fuzziness: 'AUTO',
|
|
|
|
prefix_length: 3,
|
|
|
|
minimum_should_match: sortArr.length > 1 ? '100%' : '60%'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
})
|
2022-10-20 22:44:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let whenGte
|
|
|
|
switch (when) {
|
|
|
|
case 'day':
|
|
|
|
whenGte = 'now-1d'
|
|
|
|
break
|
|
|
|
case 'week':
|
|
|
|
whenGte = 'now-7d'
|
|
|
|
break
|
|
|
|
case 'month':
|
|
|
|
whenGte = 'now-30d'
|
|
|
|
break
|
|
|
|
case 'year':
|
|
|
|
whenGte = 'now-365d'
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
sitems = await search.search({
|
|
|
|
index: 'item',
|
|
|
|
size: LIMIT,
|
|
|
|
from: decodedCursor.offset,
|
|
|
|
body: {
|
|
|
|
query: {
|
|
|
|
bool: {
|
|
|
|
must: [
|
|
|
|
...whatArr,
|
|
|
|
me
|
|
|
|
? {
|
|
|
|
bool: {
|
|
|
|
should: [
|
|
|
|
{ match: { status: 'ACTIVE' } },
|
|
|
|
{ match: { status: 'NOSATS' } },
|
|
|
|
{ match: { userId: me.id } }
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
bool: {
|
|
|
|
should: [
|
|
|
|
{ match: { status: 'ACTIVE' } },
|
|
|
|
{ match: { status: 'NOSATS' } }
|
|
|
|
]
|
|
|
|
}
|
2022-10-24 21:32:16 +00:00
|
|
|
}
|
2022-10-20 22:44:44 +00:00
|
|
|
],
|
2022-10-28 16:00:53 +00:00
|
|
|
filter: [
|
|
|
|
{
|
|
|
|
range:
|
|
|
|
{
|
|
|
|
createdAt: {
|
|
|
|
lte: decodedCursor.time,
|
|
|
|
gte: whenGte
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ range: { wvotes: { gt: -1 * ITEM_FILTER_THRESHOLD } } }
|
|
|
|
]
|
2022-10-20 22:44:44 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
sort: sortArr,
|
|
|
|
highlight: {
|
|
|
|
fields: {
|
|
|
|
title: { number_of_fragments: 0, pre_tags: [':high['], post_tags: [']'] },
|
2023-07-23 15:08:43 +00:00
|
|
|
text: { number_of_fragments: 5, order: 'score', pre_tags: [':high['], post_tags: [']'] }
|
2022-10-20 22:44:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e)
|
|
|
|
return {
|
|
|
|
cursor: null,
|
|
|
|
items: []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return highlights
|
|
|
|
const items = sitems.body.hits.hits.map(async e => {
|
|
|
|
// this is super inefficient but will suffice until we do something more generic
|
|
|
|
const item = await getItem(parent, { id: e._source.id }, { me, models })
|
|
|
|
|
2022-10-24 21:32:16 +00:00
|
|
|
item.searchTitle = (e.highlight?.title && e.highlight.title[0]) || item.title
|
2023-07-23 15:08:43 +00:00
|
|
|
item.searchText = (e.highlight?.text && e.highlight.text.join(' `...` ')) || undefined
|
2022-10-20 22:44:44 +00:00
|
|
|
|
|
|
|
return item
|
|
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
|
|
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
|
|
|
items
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|