mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #3380 from omnivore-app/fix/recreate-item
fix/recreate item
This commit is contained in:
commit
2fff9a5720
6 changed files with 101 additions and 26 deletions
|
|
@ -198,4 +198,10 @@ export class LibraryItem {
|
|||
|
||||
@Column('text')
|
||||
folder!: string
|
||||
|
||||
@Column('text')
|
||||
labelNames?: string[]
|
||||
|
||||
@Column('text')
|
||||
highlightAnnotations?: string[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ import {
|
|||
createOrUpdateLibraryItem,
|
||||
findLibraryItemsByPrefix,
|
||||
searchLibraryItems,
|
||||
softDeleteLibraryItem,
|
||||
sortParamsToSort,
|
||||
updateLibraryItem,
|
||||
updateLibraryItemReadingProgress,
|
||||
|
|
@ -532,15 +533,7 @@ export const setBookmarkArticleResolver = authorized<
|
|||
}
|
||||
|
||||
// delete the item and its metadata
|
||||
const deletedLibraryItem = await updateLibraryItem(
|
||||
articleID,
|
||||
{
|
||||
state: LibraryItemState.Deleted,
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
uid,
|
||||
pubsub
|
||||
)
|
||||
const deletedLibraryItem = await softDeleteLibraryItem(articleID, uid, pubsub)
|
||||
|
||||
analytics.track({
|
||||
userId: uid,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,32 @@ export const findOrCreateLabels = async (
|
|||
)
|
||||
}
|
||||
|
||||
export const createAndAddLabelsToLibraryItem = async (
|
||||
libraryItemId: string,
|
||||
userId: string,
|
||||
labels?: CreateLabelInput[] | null,
|
||||
rssFeedUrl?: string | null,
|
||||
source?: LabelSource
|
||||
) => {
|
||||
if (rssFeedUrl) {
|
||||
// add rss label to labels
|
||||
labels = (labels || []).concat({ name: 'RSS' })
|
||||
source = 'system'
|
||||
}
|
||||
|
||||
// save labels in item
|
||||
if (labels && labels.length > 0) {
|
||||
const newLabels = await findOrCreateLabels(labels, userId)
|
||||
|
||||
await addLabelsToLibraryItem(
|
||||
newLabels.map((l) => l.id),
|
||||
libraryItemId,
|
||||
userId,
|
||||
source
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const createAndSaveLabelsInLibraryItem = async (
|
||||
libraryItemId: string,
|
||||
userId: string,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from 'typeorm'
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'
|
||||
import { ReadingProgressDataSource } from '../datasources/reading_progress_data_source'
|
||||
import { EntityLabel } from '../entity/entity_label'
|
||||
import { Highlight } from '../entity/highlight'
|
||||
import { Label } from '../entity/label'
|
||||
import { LibraryItem, LibraryItemState } from '../entity/library_item'
|
||||
|
|
@ -18,6 +19,7 @@ import { authTrx, getColumns, queryBuilderToRawSql } from '../repository'
|
|||
import { libraryItemRepository } from '../repository/library_item'
|
||||
import { Merge } from '../util'
|
||||
import { setRecentlySavedItemInRedis } from '../utils/helpers'
|
||||
import { logger } from '../utils/logger'
|
||||
import { parseSearchQuery } from '../utils/search'
|
||||
import { addLabelsToLibraryItem } from './labels'
|
||||
|
||||
|
|
@ -708,6 +710,32 @@ export const restoreLibraryItem = async (
|
|||
)
|
||||
}
|
||||
|
||||
export const softDeleteLibraryItem = async (
|
||||
id: string,
|
||||
userId: string,
|
||||
pubsub = createPubSubClient()
|
||||
): Promise<LibraryItem> => {
|
||||
const deletedLibraryItem = await authTrx(
|
||||
async (tx) => {
|
||||
const itemRepo = tx.withRepository(libraryItemRepository)
|
||||
|
||||
// mark item as deleted
|
||||
await itemRepo.update(id, {
|
||||
state: LibraryItemState.Deleted,
|
||||
deletedAt: new Date(),
|
||||
})
|
||||
|
||||
return itemRepo.findOneByOrFail({ id })
|
||||
},
|
||||
undefined,
|
||||
userId
|
||||
)
|
||||
|
||||
await pubsub.entityDeleted(EntityType.PAGE, id, userId)
|
||||
|
||||
return deletedLibraryItem
|
||||
}
|
||||
|
||||
export const updateLibraryItem = async (
|
||||
id: string,
|
||||
libraryItem: QueryDeepPartialEntity<LibraryItem>,
|
||||
|
|
@ -719,14 +747,11 @@ export const updateLibraryItem = async (
|
|||
async (tx) => {
|
||||
const itemRepo = tx.withRepository(libraryItemRepository)
|
||||
|
||||
// reset deletedAt and archivedAt
|
||||
// reset archivedAt
|
||||
switch (libraryItem.state) {
|
||||
case LibraryItemState.Archived:
|
||||
libraryItem.archivedAt = new Date()
|
||||
break
|
||||
case LibraryItemState.Deleted:
|
||||
libraryItem.deletedAt = new Date()
|
||||
break
|
||||
case LibraryItemState.Processing:
|
||||
case LibraryItemState.Succeeded:
|
||||
libraryItem.archivedAt = null
|
||||
|
|
@ -847,15 +872,39 @@ export const createOrUpdateLibraryItem = async (
|
|||
)
|
||||
|
||||
if (existingLibraryItem) {
|
||||
const id = existingLibraryItem.id
|
||||
|
||||
try {
|
||||
// delete labels and highlights if the item was deleted
|
||||
if (existingLibraryItem.state === LibraryItemState.Deleted) {
|
||||
logger.info('Deleting labels and highlights for item', {
|
||||
id,
|
||||
})
|
||||
await tx.getRepository(Highlight).delete({
|
||||
libraryItem: { id: existingLibraryItem.id },
|
||||
})
|
||||
|
||||
await tx.getRepository(EntityLabel).delete({
|
||||
libraryItemId: existingLibraryItem.id,
|
||||
})
|
||||
|
||||
libraryItem.labelNames = []
|
||||
libraryItem.highlightAnnotations = []
|
||||
}
|
||||
} catch (error) {
|
||||
// continue to save the item even if we failed to delete labels and highlights
|
||||
logger.error('Failed to delete labels and highlights', error)
|
||||
}
|
||||
|
||||
// update existing library item
|
||||
const newItem = await repo.save({
|
||||
...libraryItem,
|
||||
id: existingLibraryItem.id,
|
||||
id,
|
||||
slug: existingLibraryItem.slug, // keep the original slug
|
||||
})
|
||||
|
||||
// delete the new item if it's different from the existing one
|
||||
if (libraryItem.id && libraryItem.id !== existingLibraryItem.id) {
|
||||
if (libraryItem.id && libraryItem.id !== id) {
|
||||
await repo.delete(libraryItem.id)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { parsePreparedContent } from '../utils/parser'
|
|||
import { contentReaderForLibraryItem } from '../utils/uploads'
|
||||
import { createPageSaveRequest } from './create_page_save_request'
|
||||
import { createHighlight } from './highlights'
|
||||
import { createAndSaveLabelsInLibraryItem } from './labels'
|
||||
import { createAndAddLabelsToLibraryItem } from './labels'
|
||||
import { createOrUpdateLibraryItem } from './library_item'
|
||||
|
||||
// where we can use APIs to fetch their underlying content.
|
||||
|
|
@ -132,7 +132,8 @@ export const savePage = async (
|
|||
)
|
||||
clientRequestId = newItem.id
|
||||
|
||||
await createAndSaveLabelsInLibraryItem(
|
||||
// merge labels
|
||||
await createAndAddLabelsToLibraryItem(
|
||||
clientRequestId,
|
||||
user.id,
|
||||
input.labels,
|
||||
|
|
@ -157,6 +158,7 @@ export const savePage = async (
|
|||
libraryItem: { id: clientRequestId },
|
||||
}
|
||||
|
||||
// merge highlights
|
||||
try {
|
||||
await createHighlight(highlight, clientRequestId, user.id)
|
||||
} catch (error) {
|
||||
|
|
@ -214,7 +216,7 @@ export const parsedContentToLibraryItem = ({
|
|||
rssFeedUrl?: string | null
|
||||
folder?: string | null
|
||||
}): DeepPartial<LibraryItem> & { originalUrl: string } => {
|
||||
logger.info('save_page: state', { url, state, itemId })
|
||||
logger.info('save_page', { url, state, itemId })
|
||||
return {
|
||||
id: itemId || undefined,
|
||||
slug,
|
||||
|
|
@ -254,5 +256,6 @@ export const parsedContentToLibraryItem = ({
|
|||
folder: folder || 'inbox',
|
||||
archivedAt:
|
||||
state === ArticleSavingRequestStatus.Archived ? new Date() : null,
|
||||
deletedAt: state === ArticleSavingRequestStatus.Deleted ? new Date() : null,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,16 +23,17 @@ import { getRepository } from '../../src/repository'
|
|||
import { createGroup, deleteGroup } from '../../src/services/groups'
|
||||
import { createLabel, deleteLabels } from '../../src/services/labels'
|
||||
import {
|
||||
createOrUpdateLibraryItem,
|
||||
createLibraryItems,
|
||||
createOrUpdateLibraryItem,
|
||||
CreateOrUpdateLibraryItemArgs,
|
||||
deleteLibraryItemById,
|
||||
deleteLibraryItemByUrl,
|
||||
deleteLibraryItems,
|
||||
deleteLibraryItemsByUserId,
|
||||
findLibraryItemById,
|
||||
findLibraryItemByUrl,
|
||||
softDeleteLibraryItem,
|
||||
updateLibraryItem,
|
||||
CreateOrUpdateLibraryItemArgs,
|
||||
} from '../../src/services/library_item'
|
||||
import { deleteUser } from '../../src/services/user'
|
||||
import * as createTask from '../../src/utils/createTask'
|
||||
|
|
@ -712,7 +713,7 @@ describe('Article API', () => {
|
|||
await deleteLibraryItemById(itemId, user.id)
|
||||
})
|
||||
|
||||
it('marks an article as deleted', async () => {
|
||||
it('soft deletes the item', async () => {
|
||||
await graphqlRequest(setBookmarkQuery(itemId, false), authToken).expect(
|
||||
200
|
||||
)
|
||||
|
|
@ -2062,11 +2063,8 @@ describe('Article API', () => {
|
|||
|
||||
// Delete some items
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await updateLibraryItem(
|
||||
items[i].id,
|
||||
{ state: LibraryItemState.Deleted, deletedAt: new Date() },
|
||||
user.id
|
||||
)
|
||||
await softDeleteLibraryItem(items[i].id, user.id)
|
||||
|
||||
deletedItems.push(items[i])
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue