mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge remote-tracking branch 'origin/main' into fix/android-reader-status-bar
This commit is contained in:
commit
abdf920b61
4 changed files with 92 additions and 55 deletions
|
|
@ -73,6 +73,7 @@ import {
|
|||
import {
|
||||
batchDelete,
|
||||
batchUpdateLibraryItems,
|
||||
countLibraryItems,
|
||||
createOrUpdateLibraryItem,
|
||||
findLibraryItemsByPrefix,
|
||||
searchLibraryItems,
|
||||
|
|
@ -847,8 +848,7 @@ export const bulkActionResolver = authorized<
|
|||
query,
|
||||
size: 0,
|
||||
}
|
||||
const searchResult = await searchLibraryItems(searchArgs, uid)
|
||||
const count = searchResult.count
|
||||
const count = await countLibraryItems(searchArgs, uid)
|
||||
if (count === 0) {
|
||||
log.info('No items found for bulk action')
|
||||
return { success: true }
|
||||
|
|
@ -856,6 +856,11 @@ export const bulkActionResolver = authorized<
|
|||
|
||||
if (count <= batchSize) {
|
||||
searchArgs.size = count
|
||||
log.info('Bulk action: updating items synchronously', {
|
||||
query,
|
||||
action,
|
||||
count,
|
||||
})
|
||||
// if there are less than 100 items, update them synchronously
|
||||
await batchUpdateLibraryItems(action, searchArgs, uid, labelIds, args)
|
||||
|
||||
|
|
@ -872,6 +877,7 @@ export const bulkActionResolver = authorized<
|
|||
args,
|
||||
batchSize,
|
||||
}
|
||||
log.info('enqueue bulk action job', data)
|
||||
const job = await enqueueBulkAction(data)
|
||||
if (!job) {
|
||||
return { errorCodes: [BulkActionErrorCode.BadRequest] }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
EntityManager,
|
||||
FindOptionsWhere,
|
||||
ObjectLiteral,
|
||||
SelectQueryBuilder,
|
||||
} from 'typeorm'
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'
|
||||
import { ReadingProgressDataSource } from '../datasources/reading_progress_data_source'
|
||||
|
|
@ -15,7 +16,12 @@ import { LibraryItem, LibraryItemState } from '../entity/library_item'
|
|||
import { BulkActionType, InputMaybe, SortParams } from '../generated/graphql'
|
||||
import { createPubSubClient, EntityType } from '../pubsub'
|
||||
import { redisDataSource } from '../redis_data_source'
|
||||
import { authTrx, getColumns, queryBuilderToRawSql } from '../repository'
|
||||
import {
|
||||
authTrx,
|
||||
getColumns,
|
||||
getRepository,
|
||||
queryBuilderToRawSql,
|
||||
} from '../repository'
|
||||
import { libraryItemRepository } from '../repository/library_item'
|
||||
import { Merge } from '../util'
|
||||
import { setRecentlySavedItemInRedis } from '../utils/helpers'
|
||||
|
|
@ -198,7 +204,7 @@ const getColumnName = (field: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const buildQuery = (
|
||||
export const buildQueryString = (
|
||||
searchQuery: LiqeQuery,
|
||||
parameters: ObjectLiteral[] = [],
|
||||
selects: Select[] = [],
|
||||
|
|
@ -268,12 +274,12 @@ export const buildQuery = (
|
|||
case InFilter.ALL:
|
||||
return null
|
||||
case InFilter.ARCHIVE:
|
||||
return 'library_item.archived_at IS NOT NULL'
|
||||
return "library_item.state = 'ARCHIVED'"
|
||||
case InFilter.TRASH:
|
||||
// return only deleted pages within 14 days
|
||||
return "library_item.deleted_at >= now() - interval '14 days'"
|
||||
return "(library_item.state = 'DELETED' AND library_item.deleted_at >= now() - interval '14 days')"
|
||||
default: {
|
||||
let sql = 'library_item.archived_at IS NULL'
|
||||
let sql = "library_item.state <> 'ARCHIVED'"
|
||||
if (useFolders) {
|
||||
const param = `folder_${parameters.length}`
|
||||
const folderSql = escapeQueryWithParameters(
|
||||
|
|
@ -568,12 +574,11 @@ export const buildQuery = (
|
|||
return serialize(searchQuery)
|
||||
}
|
||||
|
||||
export const searchLibraryItems = async (
|
||||
export const buildQuery = (
|
||||
queryBuilder: SelectQueryBuilder<LibraryItem>,
|
||||
args: SearchArgs,
|
||||
userId: string
|
||||
): Promise<{ libraryItems: LibraryItem[]; count: number }> => {
|
||||
const { from = 0, size = 10 } = args
|
||||
|
||||
) => {
|
||||
// select all columns except content
|
||||
const selects: Select[] = getColumns(libraryItemRepository)
|
||||
.map((column) => ({ column: `library_item.${column}` }))
|
||||
|
|
@ -585,13 +590,13 @@ export const searchLibraryItems = async (
|
|||
|
||||
const parameters: ObjectLiteral[] = []
|
||||
const orders: Sort[] = []
|
||||
let query: string | null = null
|
||||
let queryString: string | null = null
|
||||
|
||||
if (args.query) {
|
||||
const searchQuery = parseSearchQuery(args.query)
|
||||
|
||||
// build query and save parameters
|
||||
query = buildQuery(
|
||||
// build query string and save parameters
|
||||
queryString = buildQueryString(
|
||||
searchQuery,
|
||||
parameters,
|
||||
selects,
|
||||
|
|
@ -599,50 +604,70 @@ export const searchLibraryItems = async (
|
|||
args.useFolders
|
||||
)
|
||||
}
|
||||
queryBuilder.where('library_item.user_id = :userId', { userId })
|
||||
|
||||
// add select
|
||||
selects.forEach((select) => {
|
||||
queryBuilder.addSelect(select.column, select.alias)
|
||||
})
|
||||
|
||||
if (!args.includePending) {
|
||||
queryBuilder.andWhere("library_item.state <> 'PROCESSING'")
|
||||
}
|
||||
|
||||
if (!args.includeDeleted) {
|
||||
queryBuilder.andWhere("library_item.state <> 'DELETED'")
|
||||
}
|
||||
|
||||
if (queryString) {
|
||||
// add where clause from query string
|
||||
queryBuilder
|
||||
.andWhere(`(${queryString})`)
|
||||
.setParameters(paramtersToObject(parameters))
|
||||
}
|
||||
|
||||
// default order by saved at descending
|
||||
if (!orders.find((order) => order.by === 'library_item.saved_at')) {
|
||||
orders.push({
|
||||
by: 'library_item.saved_at',
|
||||
order: SortOrder.DESCENDING,
|
||||
nulls: 'NULLS LAST',
|
||||
})
|
||||
}
|
||||
|
||||
// add order by
|
||||
orders.forEach((order) => {
|
||||
queryBuilder.addOrderBy(order.by, order.order, order.nulls)
|
||||
})
|
||||
}
|
||||
|
||||
export const countLibraryItems = async (args: SearchArgs, userId: string) => {
|
||||
const queryBuilder =
|
||||
getRepository(LibraryItem).createQueryBuilder('library_item')
|
||||
|
||||
buildQuery(queryBuilder, args, userId)
|
||||
|
||||
return queryBuilder.getCount()
|
||||
}
|
||||
|
||||
export const searchLibraryItems = async (
|
||||
args: SearchArgs,
|
||||
userId: string
|
||||
): Promise<{ libraryItems: LibraryItem[]; count: number }> => {
|
||||
const { from = 0, size = 10 } = args
|
||||
|
||||
// add pagination and sorting
|
||||
return authTrx(
|
||||
async (tx) => {
|
||||
const queryBuilder = tx
|
||||
.createQueryBuilder(LibraryItem, 'library_item')
|
||||
.where('library_item.user_id = :userId', { userId })
|
||||
|
||||
// add select
|
||||
selects.forEach((select) => {
|
||||
queryBuilder.addSelect(select.column, select.alias)
|
||||
})
|
||||
|
||||
if (!args.includePending) {
|
||||
queryBuilder.andWhere("library_item.state <> 'PROCESSING'")
|
||||
}
|
||||
|
||||
if (!args.includeDeleted) {
|
||||
queryBuilder.andWhere("library_item.state <> 'DELETED'")
|
||||
}
|
||||
|
||||
if (query) {
|
||||
// add where clause from query
|
||||
queryBuilder
|
||||
.andWhere(`(${query})`)
|
||||
.setParameters(paramtersToObject(parameters))
|
||||
}
|
||||
const queryBuilder = tx.createQueryBuilder(LibraryItem, 'library_item')
|
||||
buildQuery(queryBuilder, args, userId)
|
||||
|
||||
const count = await queryBuilder.getCount()
|
||||
|
||||
// default order by saved at descending
|
||||
if (!orders.find((order) => order.by === 'library_item.saved_at')) {
|
||||
orders.push({
|
||||
by: 'library_item.saved_at',
|
||||
order: SortOrder.DESCENDING,
|
||||
nulls: 'NULLS LAST',
|
||||
})
|
||||
if (size === 0) {
|
||||
// return only count if size is 0 because limit 0 is not allowed in typeorm
|
||||
return { libraryItems: [], count }
|
||||
}
|
||||
|
||||
// add order by
|
||||
orders.forEach((order) => {
|
||||
queryBuilder.addOrderBy(order.by, order.order, order.nulls)
|
||||
})
|
||||
|
||||
// add pagination
|
||||
const libraryItems = await queryBuilder.skip(from).take(size).getMany()
|
||||
|
||||
return { libraryItems, count }
|
||||
|
|
@ -1043,9 +1068,9 @@ export const batchUpdateLibraryItems = async (
|
|||
const queryBuilder = em
|
||||
.createQueryBuilder(LibraryItem, 'library_item')
|
||||
.where('library_item.user_id = :userId', { userId })
|
||||
if (query) {
|
||||
if (queryString) {
|
||||
queryBuilder
|
||||
.andWhere(`(${query})`)
|
||||
.andWhere(`(${queryString})`)
|
||||
.setParameters(paramtersToObject(parameters))
|
||||
}
|
||||
return queryBuilder
|
||||
|
|
@ -1065,7 +1090,7 @@ export const batchUpdateLibraryItems = async (
|
|||
|
||||
const searchQuery = parseSearchQuery(searchArgs.query)
|
||||
const parameters: ObjectLiteral[] = []
|
||||
const query = buildQuery(searchQuery, parameters)
|
||||
const queryString = buildQueryString(searchQuery, parameters)
|
||||
|
||||
const now = new Date().toISOString()
|
||||
// build the script
|
||||
|
|
|
|||
|
|
@ -1383,6 +1383,7 @@ describe('Article API', () => {
|
|||
originalUrl: `${url}/test1`,
|
||||
itemType: PageType.File,
|
||||
archivedAt: new Date(),
|
||||
state: LibraryItemState.Archived,
|
||||
},
|
||||
{
|
||||
user,
|
||||
|
|
@ -1391,6 +1392,7 @@ describe('Article API', () => {
|
|||
slug: 'test slug 2',
|
||||
originalUrl: `${url}/test2`,
|
||||
archivedAt: new Date(),
|
||||
state: LibraryItemState.Archived,
|
||||
readingProgressBottomPercent: 100,
|
||||
},
|
||||
{
|
||||
|
|
@ -1433,6 +1435,7 @@ describe('Article API', () => {
|
|||
originalUrl: `${url}/test1`,
|
||||
subscription: 'feed',
|
||||
archivedAt: new Date(),
|
||||
state: LibraryItemState.Archived,
|
||||
},
|
||||
{
|
||||
user,
|
||||
|
|
@ -1449,6 +1452,7 @@ describe('Article API', () => {
|
|||
slug: 'test slug 3',
|
||||
originalUrl: `${url}/test3`,
|
||||
archivedAt: new Date(),
|
||||
state: LibraryItemState.Archived,
|
||||
},
|
||||
],
|
||||
user.id
|
||||
|
|
@ -1482,6 +1486,7 @@ describe('Article API', () => {
|
|||
slug: 'test slug 1',
|
||||
originalUrl: `${url}/test1`,
|
||||
deletedAt: new Date(),
|
||||
state: LibraryItemState.Deleted,
|
||||
},
|
||||
{
|
||||
user,
|
||||
|
|
@ -1491,6 +1496,7 @@ describe('Article API', () => {
|
|||
originalUrl: `${url}/test2`,
|
||||
readingProgressBottomPercent: 100,
|
||||
deletedAt: new Date(),
|
||||
state: LibraryItemState.Deleted,
|
||||
},
|
||||
{
|
||||
user,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { deleteUser } from '../../src/services/user'
|
|||
import { createTestUser } from '../db'
|
||||
import { graphqlRequest, request } from '../util'
|
||||
|
||||
describe('recent_searches resolver', () => {
|
||||
xdescribe('recent_searches resolver', () => {
|
||||
let user: User
|
||||
let authToken: string
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue