Merge pull request #3380 from omnivore-app/fix/recreate-item

fix/recreate item
This commit is contained in:
Hongbo Wu 2024-02-21 15:49:28 +08:00 committed by GitHub
commit 2fff9a5720
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 101 additions and 26 deletions

View file

@ -198,4 +198,10 @@ export class LibraryItem {
@Column('text')
folder!: string
@Column('text')
labelNames?: string[]
@Column('text')
highlightAnnotations?: string[]
}

View file

@ -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,

View file

@ -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,

View file

@ -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)
}

View file

@ -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,
}
}

View file

@ -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])
}
})