implement search function
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { DeepPartial } from 'typeorm'
|
||||
import { DeepPartial, SelectQueryBuilder } from 'typeorm'
|
||||
import { Highlight } from '../entity/highlight'
|
||||
import { Label } from '../entity/label'
|
||||
import {
|
||||
@ -15,8 +15,11 @@ import {
|
||||
HasFilter,
|
||||
InFilter,
|
||||
LabelFilter,
|
||||
LabelFilterType,
|
||||
NoFilter,
|
||||
ReadFilter,
|
||||
SortBy,
|
||||
SortOrder,
|
||||
SortParams,
|
||||
} from '../utils/search'
|
||||
|
||||
@ -102,3 +105,140 @@ export const createLibraryItem = async (
|
||||
libraryItem.wordCount ?? wordsCount(libraryItem.readableContent ?? ''),
|
||||
})
|
||||
}
|
||||
|
||||
const buildWhereClause = (
|
||||
queryBuilder: SelectQueryBuilder<LibraryItem>,
|
||||
userId: string,
|
||||
args: PageSearchArgs
|
||||
) => {
|
||||
if (args.query) {
|
||||
queryBuilder.andWhere(`tsv @@ websearch_to_tsquery(:query)`, {
|
||||
query: args.query,
|
||||
})
|
||||
}
|
||||
|
||||
if (args.typeFilter) {
|
||||
queryBuilder.andWhere(`library_item.item_type = :typeFilter`, {
|
||||
typeFilter: args.typeFilter,
|
||||
})
|
||||
}
|
||||
|
||||
if (args.inFilter !== InFilter.ALL) {
|
||||
switch (args.inFilter) {
|
||||
case InFilter.INBOX:
|
||||
queryBuilder.andWhere(`library_item.archived_at IS NULL`)
|
||||
break
|
||||
case InFilter.ARCHIVE:
|
||||
queryBuilder.andWhere(`library_item.archived_at IS NOT NULL`)
|
||||
break
|
||||
case InFilter.TRASH:
|
||||
// return only deleted pages within 14 days
|
||||
queryBuilder.andWhere(
|
||||
`library_item.state = :state AND deleted_at >= now() - interval '14 days'`,
|
||||
{
|
||||
state: LibraryItemState.Deleted,
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (args.readFilter !== ReadFilter.ALL) {
|
||||
switch (args.readFilter) {
|
||||
case ReadFilter.READ:
|
||||
queryBuilder.andWhere('library_item.reading_progress_top_percent >= 98')
|
||||
break
|
||||
case ReadFilter.UNREAD:
|
||||
queryBuilder.andWhere('library_item.reading_progress_top_percent < 98')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (args.hasFilters && args.hasFilters.length > 0) {
|
||||
args.hasFilters.forEach((filter) => {
|
||||
switch (filter) {
|
||||
case HasFilter.HIGHLIGHTS:
|
||||
queryBuilder.andWhere(`library_item.highlights IS NOT NULL`)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (args.labelFilters && args.labelFilters.length > 0) {
|
||||
const includeLabels = args.labelFilters?.filter(
|
||||
(filter) => filter.type === LabelFilterType.INCLUDE
|
||||
)
|
||||
const excludeLabels = args.labelFilters?.filter(
|
||||
(filter) => filter.type === LabelFilterType.EXCLUDE
|
||||
)
|
||||
|
||||
if (includeLabels && includeLabels.length > 0) {
|
||||
queryBuilder.andWhere(
|
||||
`library_item.id IN (SELECT library_item_id FROM library_item_label WHERE name IN (:...includeLabels))`,
|
||||
{
|
||||
includeLabels,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (excludeLabels && excludeLabels.length > 0) {
|
||||
queryBuilder.andWhere(
|
||||
`library_item.id NOT IN (SELECT library_item_id FROM library_item_label WHERE name IN (:...excludeLabels))`,
|
||||
{
|
||||
excludeLabels,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (args.dateFilters && args.dateFilters.length > 0) {
|
||||
args.dateFilters.forEach((filter) => {
|
||||
queryBuilder.andWhere(
|
||||
`library_item.${filter.field} between :startDate and :endDate`,
|
||||
{
|
||||
startDate: filter.startDate ?? new Date(0),
|
||||
endDate: filter.endDate ?? new Date(),
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (args.termFilters && args.termFilters.length > 0) {
|
||||
args.termFilters.forEach((filter) => {
|
||||
queryBuilder.andWhere(`library_item.${filter.field} = :value`, {
|
||||
value: filter.value,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const searchLibraryItems = async (
|
||||
args: PageSearchArgs,
|
||||
userId: string
|
||||
): Promise<[LibraryItem[], number] | null> => {
|
||||
const { from = 0, size = 10, sort } = args
|
||||
|
||||
// default order is descending
|
||||
const sortOrder = sort?.order || SortOrder.DESCENDING
|
||||
// default sort by saved_at
|
||||
const sortField = sort?.by || SortBy.SAVED
|
||||
|
||||
const queryBuilder = entityManager
|
||||
.createQueryBuilder(LibraryItem, 'library_item')
|
||||
.select('library_item.*')
|
||||
.where('library_item.user_id = :userId', { userId })
|
||||
|
||||
// build the where clause
|
||||
buildWhereClause(queryBuilder, userId, args)
|
||||
|
||||
// add pagination and sorting
|
||||
const libraryItems = await queryBuilder
|
||||
.orderBy(`omnivore.library_item.${sortField}`, sortOrder)
|
||||
.offset(from)
|
||||
.limit(size)
|
||||
.getMany()
|
||||
|
||||
const count = await queryBuilder.getCount()
|
||||
|
||||
return [libraryItems, count]
|
||||
}
|
||||
|
||||
@ -65,18 +65,18 @@ export interface DateFilter {
|
||||
}
|
||||
|
||||
export enum SortBy {
|
||||
SAVED = 'savedAt',
|
||||
UPDATED = 'updatedAt',
|
||||
SAVED = 'saved_at',
|
||||
UPDATED = 'updated_at',
|
||||
SCORE = '_score',
|
||||
PUBLISHED = 'publishedAt',
|
||||
READ = 'readAt',
|
||||
LISTENED = 'listenedAt',
|
||||
WORDS_COUNT = 'wordsCount',
|
||||
PUBLISHED = 'published_at',
|
||||
READ = 'read_at',
|
||||
LISTENED = 'listened_at',
|
||||
WORDS_COUNT = 'word_count',
|
||||
}
|
||||
|
||||
export enum SortOrder {
|
||||
ASCENDING = 'asc',
|
||||
DESCENDING = 'desc',
|
||||
ASCENDING = 'ASC',
|
||||
DESCENDING = 'DESC',
|
||||
}
|
||||
|
||||
export interface SortParams {
|
||||
|
||||
Reference in New Issue
Block a user