allow filter by wordsCount
This commit is contained in:
@ -649,6 +649,7 @@ export const searchResolver = authorized<
|
||||
size: first + 1, // fetch one more item to get next cursor
|
||||
sort: searchQuery.sort,
|
||||
includePending: true,
|
||||
includeContent: params.includeContent || false,
|
||||
...searchQuery,
|
||||
},
|
||||
uid
|
||||
|
||||
@ -6,7 +6,7 @@ import { LibraryItem } from '../../entity/library_item'
|
||||
import { env } from '../../env'
|
||||
import { wait } from '../../utils/helpers'
|
||||
import { logger } from '../../utils/logger'
|
||||
import { getHighlightUrl } from '../highlights'
|
||||
import { findHighlightsByLibraryItemId, getHighlightUrl } from '../highlights'
|
||||
import { IntegrationService } from './integration'
|
||||
|
||||
interface ReadwiseHighlight {
|
||||
@ -64,10 +64,12 @@ export class ReadwiseIntegration extends IntegrationService {
|
||||
): Promise<boolean> => {
|
||||
let result = true
|
||||
|
||||
const highlights = items.flatMap(this.libraryItemToReadwiseHighlight)
|
||||
const highlights = await Promise.all(
|
||||
items.map((item) => this.libraryItemToReadwiseHighlight(item))
|
||||
)
|
||||
// If there are no highlights, we will skip the sync
|
||||
if (highlights.length > 0) {
|
||||
result = await this.syncWithReadwise(integration.token, highlights)
|
||||
result = await this.syncWithReadwise(integration.token, highlights.flat())
|
||||
}
|
||||
|
||||
// update integration syncedAt if successful
|
||||
@ -84,10 +86,16 @@ export class ReadwiseIntegration extends IntegrationService {
|
||||
return result
|
||||
}
|
||||
|
||||
libraryItemToReadwiseHighlight = (item: LibraryItem): ReadwiseHighlight[] => {
|
||||
if (!item.highlights) return []
|
||||
libraryItemToReadwiseHighlight = async (
|
||||
item: LibraryItem
|
||||
): Promise<ReadwiseHighlight[]> => {
|
||||
let highlights = item.highlights
|
||||
if (!highlights) {
|
||||
highlights = await findHighlightsByLibraryItemId(item.id, item.user.id)
|
||||
}
|
||||
|
||||
const category = item.siteName === 'Twitter' ? 'tweets' : 'articles'
|
||||
return item.highlights
|
||||
return highlights
|
||||
.map((highlight) => {
|
||||
// filter out highlights that are not of type highlight or have no quote
|
||||
if (
|
||||
|
||||
@ -6,7 +6,7 @@ import { Label } from '../entity/label'
|
||||
import { LibraryItem, LibraryItemState } from '../entity/library_item'
|
||||
import { BulkActionType } from '../generated/graphql'
|
||||
import { createPubSubClient, EntityType } from '../pubsub'
|
||||
import { authTrx } from '../repository'
|
||||
import { authTrx, getColumns } from '../repository'
|
||||
import { libraryItemRepository } from '../repository/library_item'
|
||||
import { wordsCount } from '../utils/helpers'
|
||||
import {
|
||||
@ -17,6 +17,7 @@ import {
|
||||
LabelFilter,
|
||||
LabelFilterType,
|
||||
NoFilter,
|
||||
RangeFilter,
|
||||
ReadFilter,
|
||||
Sort,
|
||||
SortBy,
|
||||
@ -42,6 +43,7 @@ export interface SearchArgs {
|
||||
recommendedBy?: string
|
||||
includeContent?: boolean
|
||||
noFilters?: NoFilter[]
|
||||
rangeFilters?: RangeFilter[]
|
||||
}
|
||||
|
||||
export interface SearchResultItem {
|
||||
@ -258,6 +260,22 @@ const buildWhereClause = (
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (args.includeContent) {
|
||||
queryBuilder.addSelect('library_item.readableContent')
|
||||
}
|
||||
|
||||
if (args.rangeFilters && args.rangeFilters.length > 0) {
|
||||
args.rangeFilters.forEach((filter, i) => {
|
||||
const param = `range_${filter.field}_${i}`
|
||||
queryBuilder.andWhere(
|
||||
`library_item.${filter.field} ${filter.operator} ${param}`,
|
||||
{
|
||||
[param]: filter.value,
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const searchLibraryItems = async (
|
||||
@ -271,11 +289,20 @@ export const searchLibraryItems = async (
|
||||
// default sort by saved_at
|
||||
const sortField = sort?.by || SortBy.SAVED
|
||||
|
||||
const selectColumns = getColumns(libraryItemRepository)
|
||||
.map((column) => `library_item.${column}`)
|
||||
.filter(
|
||||
(column) =>
|
||||
column !== 'library_item.readableContent' &&
|
||||
column !== 'library_item.originalContent'
|
||||
)
|
||||
|
||||
// add pagination and sorting
|
||||
return authTrx(
|
||||
async (tx) => {
|
||||
const queryBuilder = tx
|
||||
.createQueryBuilder(LibraryItem, 'library_item')
|
||||
.select(selectColumns)
|
||||
.where('library_item.user_id = :userId', { userId })
|
||||
|
||||
// build the where clause
|
||||
|
||||
@ -40,6 +40,7 @@ export interface SearchFilter {
|
||||
ids: string[]
|
||||
recommendedBy?: string
|
||||
noFilters: NoFilter[]
|
||||
rangeFilters: RangeFilter[]
|
||||
}
|
||||
|
||||
export enum LabelFilterType {
|
||||
@ -63,6 +64,12 @@ export interface DateFilter {
|
||||
endDate?: Date
|
||||
}
|
||||
|
||||
export interface RangeFilter {
|
||||
field: string
|
||||
operator: string
|
||||
value: number
|
||||
}
|
||||
|
||||
export enum SortBy {
|
||||
SAVED = 'savedAt',
|
||||
UPDATED = 'updatedAt',
|
||||
@ -255,6 +262,40 @@ const parseDateFilter = (
|
||||
}
|
||||
}
|
||||
|
||||
const parseRangeFilter = (
|
||||
field: string,
|
||||
str?: string
|
||||
): RangeFilter | undefined => {
|
||||
if (str === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
switch (field.toUpperCase()) {
|
||||
case 'WORDSCOUNT':
|
||||
field = 'wordCount'
|
||||
break
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
|
||||
const operatorRegex = /([<>]=?)/
|
||||
const operator = str.match(operatorRegex)?.[0]
|
||||
if (!operator) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const value = str.replace(operatorRegex, '')
|
||||
if (!value) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
field,
|
||||
operator,
|
||||
value: Number(value),
|
||||
}
|
||||
}
|
||||
|
||||
const parseFieldFilter = (
|
||||
field: string,
|
||||
str?: string
|
||||
@ -323,6 +364,7 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
||||
matchFilters: [],
|
||||
ids: [],
|
||||
noFilters: [],
|
||||
rangeFilters: [],
|
||||
}
|
||||
|
||||
if (!searchQuery) {
|
||||
@ -337,6 +379,7 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
||||
matchFilters: [],
|
||||
ids: [],
|
||||
noFilters: [],
|
||||
rangeFilters: [],
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,6 +407,7 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
||||
'site',
|
||||
'note',
|
||||
'rss',
|
||||
'wordCount',
|
||||
],
|
||||
tokenize: true,
|
||||
})
|
||||
@ -460,6 +504,11 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
||||
case 'mode':
|
||||
// mode is ignored and used only by the frontend
|
||||
break
|
||||
case 'wordCount': {
|
||||
const rangeFilter = parseRangeFilter(keyword.keyword, keyword.value)
|
||||
rangeFilter && result.rangeFilters.push(rangeFilter)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user