diff --git a/packages/api/src/entity/library_item.ts b/packages/api/src/entity/library_item.ts index 597d8f4c8..4399100e5 100644 --- a/packages/api/src/entity/library_item.ts +++ b/packages/api/src/entity/library_item.ts @@ -103,7 +103,7 @@ export class LibraryItem { readAt?: Date | null @UpdateDateColumn() - updatedAt?: Date | null + updatedAt!: Date @Column('text', { nullable: true }) itemLanguage?: string | null diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index e2ac8b786..1f5e7eb36 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -4,6 +4,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-floating-promises */ import { Readability } from '@omnivore/readability' +import { DeepPartial } from 'typeorm' +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity' import { LibraryItem, LibraryItemState, @@ -590,13 +592,13 @@ export const saveArticleReadingProgressResolver = authorized< : undefined // If setting to zero we accept the update, otherwise we require it // be greater than the current reading progress. - const updatedPart = { + const updatedPart: DeepPartial = { readingProgressBottomPercent: readingProgressPercent === 0 ? 0 : Math.max( readingProgressPercent, - libraryItem.readingProgressTopPercent + libraryItem.readingProgressBottomPercent ), readingProgressHighestReadAnchor: readingProgressAnchorIndex === 0 @@ -726,7 +728,7 @@ export const updatesSinceResolver = authorized< from: Number(startCursor), size: size + 1, // fetch one more item to get next cursor includeDeleted: true, - dateFilters: [{ field: 'updatedAt', startDate }], + dateFilters: [{ field: 'updated_at', startDate }], sort, }, uid diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index b0b4b19ce..68215663b 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -23,11 +23,15 @@ export const createHighlight = async ( userId: string, pubsub = createPubSubClient() ) => { - const newHighlight = await authTrx(async (tx) => { - return tx - .withRepository(highlightRepository) - .createAndSave(highlight, libraryItemId, userId) - }) + const newHighlight = await authTrx( + async (tx) => { + return tx + .withRepository(highlightRepository) + .createAndSave(highlight, libraryItemId, userId) + }, + undefined, + userId + ) await pubsub.entityCreated( EntityType.HIGHLIGHT, diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 3c6489afb..94de6dbe0 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -303,14 +303,17 @@ export const findLibraryItemById = async ( id: string, userId: string ): Promise => { - return authTrx(async (tx) => - tx - .createQueryBuilder(LibraryItem, 'library_item') - .leftJoinAndSelect('library_item.labels', 'labels') - .leftJoinAndSelect('library_item.highlights', 'highlights') - .where('library_item.user_id = :userId', { userId }) - .andWhere('library_item.id = :id', { id }) - .getOne() + return authTrx( + async (tx) => + tx + .createQueryBuilder(LibraryItem, 'library_item') + .leftJoinAndSelect('library_item.labels', 'labels') + .leftJoinAndSelect('library_item.highlights', 'highlights') + .where('library_item.user_id = :userId', { userId }) + .andWhere('library_item.id = :id', { id }) + .getOne(), + undefined, + userId ) } diff --git a/packages/api/src/utils/helpers.ts b/packages/api/src/utils/helpers.ts index c27ccfb0a..5a4cf5d59 100644 --- a/packages/api/src/utils/helpers.ts +++ b/packages/api/src/utils/helpers.ts @@ -240,7 +240,7 @@ export const libraryItemToArticle = (item: LibraryItem): Article => ({ image: item.thumbnail, contentReader: item.contentReader as unknown as ContentReader, readingProgressAnchorIndex: item.readingProgressHighestReadAnchor, - readingProgressPercent: item.readingProgressTopPercent, + readingProgressPercent: item.readingProgressBottomPercent, highlights: item.highlights?.map(highlightDataToHighlight) || [], uploadFileId: item.uploadFile?.id, pageType: item.itemType as unknown as PageType, @@ -253,7 +253,7 @@ export const libraryItemToSearchItem = (item: LibraryItem): SearchItem => ({ content: item.readableContent, isArchived: item.state === LibraryItemState.Archived, pageType: item.itemType as unknown as PageType, - readingProgressPercent: item.readingProgressTopPercent, + readingProgressPercent: item.readingProgressBottomPercent, contentReader: item.contentReader as unknown as ContentReader, readingProgressAnchorIndex: item.readingProgressHighestReadAnchor, subscription: item.subscription?.name, diff --git a/packages/api/test/resolvers/article.test.ts b/packages/api/test/resolvers/article.test.ts index 05b874f11..daac10f6b 100644 --- a/packages/api/test/resolvers/article.test.ts +++ b/packages/api/test/resolvers/article.test.ts @@ -30,11 +30,7 @@ import { deleteUser } from '../../src/services/user' import * as createTask from '../../src/utils/createTask' import * as uploads from '../../src/utils/uploads' import { createTestLibraryItem, createTestUser } from '../db' -import { - generateFakeUuid, - graphqlRequest, - request -} from '../util' +import { generateFakeUuid, graphqlRequest, request } from '../util' chai.use(chaiString) @@ -532,25 +528,22 @@ describe('Article API', () => { await deleteLibraryItemByUrl(url, user.id) }) - it('it should return that page in the GetArticles Query', async () => { + it('it should return that page in the Search Query', async () => { await graphqlRequest( savePageQuery(url, title, originalContent), authToken ).expect(200) // Save a link, then archive it - let allLinks = await graphqlRequest( - searchQuery(''), - authToken - ).expect(200) - const justSavedId = allLinks.body.data.articles.edges[0].node.id + let allLinks = await graphqlRequest(searchQuery(''), authToken).expect( + 200 + ) + const justSavedId = allLinks.body.data.search.edges[0].node.id await archiveLink(authToken, justSavedId) // test the negative case, ensuring the archive link wasn't returned - allLinks = await graphqlRequest(searchQuery(''), authToken).expect( - 200 - ) - expect(allLinks.body.data.articles.edges[0]?.node?.url).to.not.eq(url) + allLinks = await graphqlRequest(searchQuery(''), authToken).expect(200) + expect(allLinks.body.data.search.edges[0]?.node?.url).to.not.eq(url) // Now save the link again, and ensure it is returned await graphqlRequest( @@ -558,10 +551,8 @@ describe('Article API', () => { authToken ).expect(200) - allLinks = await graphqlRequest(searchQuery(''), authToken).expect( - 200 - ) - expect(allLinks.body.data.articles.edges[0].node.url).to.eq(url) + allLinks = await graphqlRequest(searchQuery(''), authToken).expect(200) + expect(allLinks.body.data.search.edges[0].node.url).to.eq(url) }) }) @@ -633,9 +624,6 @@ describe('Article API', () => { }) describe('setBookmarkArticle', () => { - let query = '' - let articleId = '' - let bookmark = true let itemId: string before(async () => { @@ -654,21 +642,12 @@ describe('Article API', () => { await deleteLibraryItemById(itemId, user.id) }) - beforeEach(() => { - query = setBookmarkQuery(articleId, bookmark) - }) - - context('when we unset a bookmark on an article', () => { - before(() => { - articleId = itemId - bookmark = false - }) - - it('should delete an article', async () => { - await graphqlRequest(query, authToken).expect(200) - const item = await findLibraryItemById(articleId, user.id) - expect(item?.state).to.eql(LibraryItemState.Deleted) - }) + it('marks an article as deleted', async () => { + await graphqlRequest(setBookmarkQuery(itemId, false), authToken).expect( + 200 + ) + const item = await findLibraryItemById(itemId, user.id) + expect(item?.state).to.eql(LibraryItemState.Deleted) }) }) @@ -721,7 +700,7 @@ describe('Article API', () => { expect( res.body.data.saveArticleReadingProgress.updatedArticle .readingProgressTopPercent - ).to.be.null + ).to.eq(0) }) it('saves topPercent if defined', async () => { @@ -834,13 +813,15 @@ describe('Article API', () => { // Create some test highlights const highlightToSave: DeepPartial = { patch: 'test patch', - shortId: 'test shortId', + shortId: `test shortId${i}`, user, quote: '

search highlight

', - createdAt: new Date(), - updatedAt: new Date(), } - const highlight = await createHighlight(highlightToSave, item.id, user.id) + const highlight = await createHighlight( + highlightToSave, + item.id, + user.id + ) highlights.push(highlight) } }) @@ -879,23 +860,6 @@ describe('Article API', () => { }) }) - context('when type:highlights is in the query', () => { - before(() => { - keyword = `'${searchedKeyword}' type:highlights` - }) - - it('should return highlights in descending order', async () => { - const res = await graphqlRequest(query, authToken).expect(200) - - expect(res.body.data.search.edges.length).to.eq(5) - expect(res.body.data.search.edges[0].node.id).to.eq(highlights[4].id) - expect(res.body.data.search.edges[1].node.id).to.eq(highlights[3].id) - expect(res.body.data.search.edges[2].node.id).to.eq(highlights[2].id) - expect(res.body.data.search.edges[3].node.id).to.eq(highlights[1].id) - expect(res.body.data.search.edges[4].node.id).to.eq(highlights[0].id) - }) - }) - context('when is:unread is in the query', () => { before(() => { keyword = `'${searchedKeyword}' is:unread` @@ -1035,19 +999,20 @@ describe('Article API', () => { slug: '', readableContent: '

test

', originalUrl: `https://blog.omnivore.app/p/updates-since-${i}`, + user, } const item = await createLibraryItem(itemToSave, user.id) items.push(item) } // set the since to be the timestamp before deletion - since = items[4].updatedAt!.toISOString() + since = items[4].updatedAt.toISOString() // Delete some pages for (let i = 0; i < 3; i++) { await updateLibraryItem( items[i].id, - { state: LibraryItemState.Deleted }, + { state: LibraryItemState.Deleted, deletedAt: new Date() }, user.id ) deletedItems.push(items[i]) @@ -1110,9 +1075,7 @@ describe('Article API', () => { readableContent: '

test

', slug: '', state: - i == 0 - ? LibraryItemState.Failed - : LibraryItemState.Succeeded, + i == 0 ? LibraryItemState.Failed : LibraryItemState.Succeeded, originalUrl: `https://blog.omnivore.app/p/bulk-action-${i}`, }, user.id diff --git a/packages/api/test/resolvers/features.test.ts b/packages/api/test/resolvers/features.test.ts index 7d37858ea..5f847337e 100644 --- a/packages/api/test/resolvers/features.test.ts +++ b/packages/api/test/resolvers/features.test.ts @@ -2,7 +2,6 @@ import { expect } from 'chai' import * as jwt from 'jsonwebtoken' import 'mocha' import sinon, { SinonFakeTimers } from 'sinon' -import { Feature } from '../../src/entity/feature' import { User } from '../../src/entity/user' import { env } from '../../src/env' import { userRepository } from '../../src/repository/user' diff --git a/packages/db/migrations/0118.do.library_item.sql b/packages/db/migrations/0118.do.library_item.sql index 5a5fd4134..7a9cdef06 100755 --- a/packages/db/migrations/0118.do.library_item.sql +++ b/packages/db/migrations/0118.do.library_item.sql @@ -27,7 +27,7 @@ CREATE TABLE omnivore.library_item ( archived_at timestamptz, deleted_at timestamptz, read_at timestamptz, - updated_at timestamptz, + updated_at timestamptz NOT NULL DEFAULT current_timestamp, item_language text, word_count integer, site_name text,