From 9857af27e2135b01d92001269a22aa80444b3f6d Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 6 Mar 2023 15:26:04 +0800 Subject: [PATCH] Add labels and state to createArticle API --- packages/api/src/generated/graphql.ts | 2 + packages/api/src/generated/schema.graphql | 2 + packages/api/src/resolvers/article/index.ts | 39 +++++++++++-------- packages/api/src/schema.ts | 2 + packages/api/src/services/save_file.ts | 35 +++++++++++++++-- packages/api/test/resolvers/article.test.ts | 16 +++++++- packages/api/test/services/save_email.test.ts | 2 +- .../services/save_newsletter_email.test.ts | 4 +- 8 files changed, 79 insertions(+), 23 deletions(-) diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index da0ef3fba..3c9449a96 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -265,9 +265,11 @@ export enum CreateArticleErrorCode { export type CreateArticleInput = { articleSavingRequestId?: InputMaybe; + labels?: InputMaybe>; preparedDocument?: InputMaybe; skipParsing?: InputMaybe; source?: InputMaybe; + state?: InputMaybe; uploadFileId?: InputMaybe; url: Scalars['String']; }; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 1ae8efa8d..82d0ef913 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -223,9 +223,11 @@ enum CreateArticleErrorCode { input CreateArticleInput { articleSavingRequestId: ID + labels: [CreateLabelInput!] preparedDocument: PreparedDocumentInput skipParsing: Boolean source: String + state: ArticleSavingRequestStatus uploadFileId: ID url: String! } diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index c7e703c10..307f737e6 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -101,6 +101,7 @@ import { makeStorageFilePublic, } from '../../utils/uploads' import { WithDataSourcesContext } from '../types' +import { createLabels } from '../../services/labels' enum ArticleFormat { Markdown = 'markdown', @@ -146,6 +147,8 @@ export const createArticleResolver = authorized< uploadFileId, skipParsing, source, + state, + labels: inputLabels, }, }, ctx @@ -219,6 +222,19 @@ export const createArticleResolver = authorized< isArchived: false, }, } + // save state + let archivedAt = + state === ArticleSavingRequestStatus.Archived ? new Date() : null + if (pageId) { + const reminder = await models.reminder.getByRequestId(uid, pageId) + if (reminder && reminder.archiveUntil) { + archivedAt = new Date() + } + } + // add labels to page + const labels = inputLabels + ? await createLabels(ctx, inputLabels) + : undefined if (uploadFileId) { /* We do not trust the values from client, lookup upload file by querying @@ -248,7 +264,7 @@ export const createArticleResolver = authorized< source !== 'puppeteer-parse' && FORCE_PUPPETEER_URLS.some((regex) => regex.test(url)) ) { - await createPageSaveRequest({ userId: uid, url }) + await createPageSaveRequest({ userId: uid, url, archivedAt, labels }) return DUMMY_RESPONSE } else if (!skipParsing && preparedDocument?.document) { const parseResults = await traceAs>( @@ -264,7 +280,7 @@ export const createArticleResolver = authorized< } else if (!preparedDocument?.document) { // We have a URL but no document, so we try to send this to puppeteer // and return a dummy response. - await createPageSaveRequest({ userId: uid, url }) + await createPageSaveRequest({ userId: uid, url, archivedAt, labels }) return DUMMY_RESPONSE } @@ -287,14 +303,6 @@ export const createArticleResolver = authorized< saveTime, }) - let archive = false - if (pageId) { - const reminder = await models.reminder.getByRequestId(uid, pageId) - if (reminder) { - archive = reminder.archiveUntil || false - } - } - log.info('New article saving', { parsedArticle: Object.assign({}, articleToSave, { content: undefined, @@ -308,7 +316,6 @@ export const createArticleResolver = authorized< }, }) - let uploadFileUrlOverride = '' if (uploadFileId) { const uploadFileData = await authTrx(async (tx) => { return models.uploadFile.setFileUploadComplete(uploadFileId, tx) @@ -322,12 +329,11 @@ export const createArticleResolver = authorized< pageId ) } - uploadFileUrlOverride = await makeStorageFilePublic( - uploadFileData.id, - uploadFileData.fileName - ) + await makeStorageFilePublic(uploadFileData.id, uploadFileData.fileName) } - + // save page's state and labels + articleToSave.archivedAt = archivedAt + articleToSave.labels = labels if ( pageId || (pageId = ( @@ -338,7 +344,6 @@ export const createArticleResolver = authorized< )?.id) ) { // update existing page's state from processing to succeeded - articleToSave.archivedAt = archive ? saveTime : null const updated = await updatePage(pageId, articleToSave, { ...ctx, uid, diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index a09570776..afcd9c787 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -488,6 +488,8 @@ const schema = gql` uploadFileId: ID skipParsing: Boolean source: String + state: ArticleSavingRequestStatus + labels: [CreateLabelInput!] } enum CreateArticleErrorCode { UNABLE_TO_FETCH diff --git a/packages/api/src/services/save_file.ts b/packages/api/src/services/save_file.ts index 7e12f7150..e7624b786 100644 --- a/packages/api/src/services/save_file.ts +++ b/packages/api/src/services/save_file.ts @@ -2,8 +2,15 @@ import { Knex } from 'knex' import { PubsubClient } from '../datalayer/pubsub' import { UserData } from '../datalayer/user/model' import { homePageURL } from '../env' -import { SaveErrorCode, SaveFileInput, SaveResult } from '../generated/graphql' +import { + ArticleSavingRequestStatus, + SaveErrorCode, + SaveFileInput, + SaveResult, +} from '../generated/graphql' import { DataModels } from '../resolvers/types' +import { createLabels } from './labels' +import { updatePage } from '../elastic/pages' import { getStorageFileDetails } from '../utils/uploads' type SaveContext = { @@ -22,7 +29,7 @@ export const saveFile = async ( input: SaveFileInput ): Promise => { console.log('saving file with input', input) - + const pageId = input.clientRequestId const uploadFile = await ctx.models.uploadFile.getWhere({ id: input.uploadFileId, userId: saver.id, @@ -46,7 +53,29 @@ export const saveFile = async ( } } - // TODO: save labels and archive state + // save state + const archivedAt = + input.state === ArticleSavingRequestStatus.Archived ? new Date() : null + // add labels to page + const labels = input.labels + ? await createLabels({ ...ctx, uid: saver.id }, input.labels) + : undefined + if (input.state || input.labels) { + const updated = await updatePage( + pageId, + { + archivedAt, + labels, + }, + ctx + ) + if (!updated) { + console.log('error updating page', pageId) + return { + errorCodes: [SaveErrorCode.Unknown], + } + } + } return { clientRequestId: input.clientRequestId, diff --git a/packages/api/test/resolvers/article.test.ts b/packages/api/test/resolvers/article.test.ts index cbcbaddae..88671648d 100644 --- a/packages/api/test/resolvers/article.test.ts +++ b/packages/api/test/resolvers/article.test.ts @@ -39,6 +39,7 @@ import { } from '../util' import sinon from 'sinon' import * as createTask from '../../src/utils/createTask' +import * as uploads from '../../src/utils/uploads' chai.use(chaiString) @@ -643,6 +644,7 @@ describe('Article API', () => { }) it('saves the labels and archives the page', async () => { + url = 'https://blog.omnivore.app/new-url-2' const state = ArticleSavingRequestStatus.Archived const labels = ['test name', 'test name 2'] await graphqlRequest( @@ -849,15 +851,27 @@ describe('Article API', () => { }) }) - xdescribe('SaveFile', () => { + describe('SaveFile', () => { let query = '' let url = '' let uploadFileId = '' + before(() => { + sinon.replace( + uploads, + 'getStorageFileDetails', + sinon.fake.resolves({ fileUrl: 'fake url', md5Hash: 'fake hash' }) + ) + }) + beforeEach(() => { query = saveFileQuery(url, uploadFileId) }) + after(() => { + sinon.restore() + }) + context('when the file is not uploaded', () => { before(async () => { url = 'fake url' diff --git a/packages/api/test/services/save_email.test.ts b/packages/api/test/services/save_email.test.ts index 975c9ad0d..a98cbd659 100644 --- a/packages/api/test/services/save_email.test.ts +++ b/packages/api/test/services/save_email.test.ts @@ -22,7 +22,7 @@ describe('saveEmail', () => { }) it('doesnt fail if saved twice', async () => { - nock('https://blog.omnivore.app').get('/fake-url').reply(404) + nock('https://blog.omnivore.app').get('/fake-url').reply(200) const url = 'https://blog.omnivore.app/fake-url' const title = 'fake title' diff --git a/packages/api/test/services/save_newsletter_email.test.ts b/packages/api/test/services/save_newsletter_email.test.ts index 7859aed45..a1b0fbd92 100644 --- a/packages/api/test/services/save_newsletter_email.test.ts +++ b/packages/api/test/services/save_newsletter_email.test.ts @@ -50,7 +50,8 @@ describe('saveNewsletterEmail', () => { }) it('adds the newsletter to the library', async () => { - nock('https://blog.omnivore.app').get('/fake-url').reply(404) + nock('https://blog.omnivore.app').get('/fake-url').reply(200) + nock('https://blog.omnivore.app').head('/fake-url').reply(200) const url = 'https://blog.omnivore.app/fake-url' await saveNewsletterEmail( @@ -88,6 +89,7 @@ describe('saveNewsletterEmail', () => { }) it('adds a Newsletter label to that page', async () => { + nock('https://blog.omnivore.app').get('/new-fake-url').reply(200) const url = 'https://blog.omnivore.app/new-fake-url' const newLabel = { name: 'Newsletter',