From e5bc68ca9ecf0c5f99b88f5be7e07a1246e747cb Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Sat, 21 May 2022 12:05:28 -0700 Subject: [PATCH] Simplify the API to create pages from PDFs This creates a stub page when an upload is initiated, so the SaveFile (or create article from file) APIs do not need to be called. The PDF uploaded trigger fired by GCS will mark the upload as completed and update the page status. --- .../api/src/resolvers/upload_files/index.ts | 35 +++++- packages/api/src/routers/svc/content.ts | 20 ++++ packages/api/src/services/save_file.ts | 112 +++++++++--------- 3 files changed, 110 insertions(+), 57 deletions(-) diff --git a/packages/api/src/resolvers/upload_files/index.ts b/packages/api/src/resolvers/upload_files/index.ts index 39720bc18..4e0c4cb63 100644 --- a/packages/api/src/resolvers/upload_files/index.ts +++ b/packages/api/src/resolvers/upload_files/index.ts @@ -5,6 +5,7 @@ import { MutationUploadFileRequestArgs, UploadFileStatus, UploadFileRequestErrorCode, + ArticleSavingRequestStatus, } from '../../generated/graphql' import { WithDataSourcesContext } from '../types' import { @@ -15,13 +16,17 @@ import path from 'path' import normalizeUrl from 'normalize-url' import { analytics } from '../../utils/analytics' import { env } from '../../env' +import { createPage } from '../../elastic/pages' +import { PageType } from '../../elastic/types' +import { generateSlug } from '../../utils/helpers' export const uploadFileRequestResolver: ResolverFn< UploadFileRequestResult, unknown, WithDataSourcesContext, MutationUploadFileRequestArgs -> = async (_obj, { input }, { models, kx, claims }) => { +> = async (_obj, { input }, ctx) => { + const { models, kx, claims } = ctx let uploadFileData: { id: string | null } = { id: null, } @@ -39,16 +44,19 @@ export const uploadFileRequestResolver: ResolverFn< }, }) + let title: string let fileName: string try { const url = normalizeUrl(new URL(input.url).href, { stripHash: true, stripWWW: false, }) + title = decodeURI(path.basename(new URL(url).pathname, '.pdf')) fileName = decodeURI(path.basename(new URL(url).pathname)).replace( /[^a-zA-Z0-9-_.]/g, '' ) + if (!fileName) { fileName = 'content.pdf' } @@ -73,6 +81,31 @@ export const uploadFileRequestResolver: ResolverFn< uploadFilePathName, input.contentType ) + + const pageId = await createPage( + { + id: '', + url: input.url, + userId: claims.uid, + title: title, + hash: uploadFilePathName, + content: '', + pageType: PageType.File, + uploadFileId: uploadFileData.id, + slug: generateSlug(uploadFilePathName), + createdAt: new Date(), + savedAt: new Date(), + readingProgressPercent: 0, + readingProgressAnchorIndex: 0, + state: ArticleSavingRequestStatus.Processing, + }, + ctx + ) + + if (!pageId) { + return { errorCodes: [UploadFileRequestErrorCode.FailedCreate] } + } + return { id: uploadFileData.id, uploadSignedUrl } } else { return { errorCodes: [UploadFileRequestErrorCode.FailedCreate] } diff --git a/packages/api/src/routers/svc/content.ts b/packages/api/src/routers/svc/content.ts index efde65410..30956b323 100644 --- a/packages/api/src/routers/svc/content.ts +++ b/packages/api/src/routers/svc/content.ts @@ -8,6 +8,10 @@ import { } from '../../datalayer/pubsub' import { Page } from '../../elastic/types' import { getPageByParam, updatePage } from '../../elastic/pages' +import { ArticleSavingRequestStatus } from '../../generated/graphql' +import { initModels } from '../../server' +import { kx } from '../../datalayer/knex_config' +import { setClaims } from '../../datalayer/helpers' interface UpdateContentMessage { fileId: string @@ -65,6 +69,22 @@ export function contentServiceRouter() { if (msg.author) pageToUpdate.author = msg.author if (msg.description) pageToUpdate.description = msg.description + // This event is fired after the file is fully uploaded, + // so along with upadting content, we mark it as + // succeeded. + pageToUpdate.state = ArticleSavingRequestStatus.Succeeded + + try { + const models = initModels(kx, false) + const uploadFileData = await kx.transaction(async (tx) => { + await setClaims(tx, page.userId) + return models.uploadFile.setFileUploadComplete(fileId, tx) + }) + console.log('updated uploadFileData', uploadFileData) + } catch (error) { + console.log('error marking file upload as completed', error) + } + const result = await updatePage(page.id, pageToUpdate, { pubsub: createPubSubClient(), uid: page.userId, diff --git a/packages/api/src/services/save_file.ts b/packages/api/src/services/save_file.ts index 874ca8e1d..cc7688b34 100644 --- a/packages/api/src/services/save_file.ts +++ b/packages/api/src/services/save_file.ts @@ -31,9 +31,9 @@ export const saveFile = async ( ): Promise => { console.log('saving file with input', input) - /* We do not trust the values from client, lookup upload file by querying - * with filtering on user ID and URL to verify client's uploadFileId is valid. - */ + // /* We do not trust the values from client, lookup upload file by querying + // * with filtering on user ID and URL to verify client's uploadFileId is valid. + // */ const uploadFile = await ctx.models.uploadFile.getWhere({ id: input.uploadFileId, userId: saver.id, @@ -53,63 +53,63 @@ export const saveFile = async ( return ctx.models.uploadFile.setFileUploadComplete(input.uploadFileId, tx) }) - if (!uploadFileData || !uploadFileData.id || !uploadFileData.fileName) { - console.log('error completing upload file request', input) - return { - errorCodes: [SaveErrorCode.Unknown], - } - } + // if (!uploadFileData || !uploadFileData.id || !uploadFileData.fileName) { + // console.log('error completing upload file request', input) + // return { + // errorCodes: [SaveErrorCode.Unknown], + // } + // } - const uploadFileUrlOverride = await makeStorageFilePublic( - uploadFileData.id, - uploadFileData.fileName - ) + // // const uploadFileUrlOverride = await makeStorageFilePublic( + // // uploadFileData.id, + // // uploadFileData.fileName + // // ) - const matchedUserArticleRecord = await getPageByParam({ - userId: saver.id, - url: uploadFileUrlOverride, - state: ArticleSavingRequestStatus.Succeeded, - }) + // const matchedUserArticleRecord = await getPageByParam({ + // userId: saver.id, + // url: uploadFileData.url, + // state: ArticleSavingRequestStatus.Succeeded, + // }) - if (matchedUserArticleRecord) { - await updatePage( - matchedUserArticleRecord.id, - { - savedAt: new Date(), - archivedAt: null, - }, - ctx - ) - input.clientRequestId = matchedUserArticleRecord.id - } else { - const pageId = await createPage( - { - url: uploadFileUrlOverride, - title: uploadFile.fileName, - hash: uploadFileDetails.md5Hash, - content: '', - pageType: PageType.File, - uploadFileId: input.uploadFileId, - slug: generateSlug(uploadFile.fileName), - userId: saver.id, - id: input.clientRequestId, - createdAt: new Date(), - savedAt: new Date(), - readingProgressPercent: 0, - readingProgressAnchorIndex: 0, - state: ArticleSavingRequestStatus.Succeeded, - }, - ctx - ) + // if (matchedUserArticleRecord) { + // await updatePage( + // matchedUserArticleRecord.id, + // { + // savedAt: new Date(), + // archivedAt: null, + // }, + // ctx + // ) + // input.clientRequestId = matchedUserArticleRecord.id + // } else { + // const pageId = await createPage( + // { + // url: uploadFile.url, + // title: uploadFile.fileName, + // hash: uploadFileDetails.md5Hash, + // content: '', + // pageType: PageType.File, + // uploadFileId: input.uploadFileId, + // slug: generateSlug(uploadFile.fileName), + // userId: saver.id, + // id: input.clientRequestId, + // createdAt: new Date(), + // savedAt: new Date(), + // readingProgressPercent: 0, + // readingProgressAnchorIndex: 0, + // state: ArticleSavingRequestStatus.Succeeded, + // }, + // ctx + // ) - if (!pageId) { - console.log('error creating page in elastic', input) - return { - errorCodes: [SaveErrorCode.Unknown], - } - } - input.clientRequestId = pageId - } + // if (!pageId) { + // console.log('error creating page in elastic', input) + // return { + // errorCodes: [SaveErrorCode.Unknown], + // } + // } + // input.clientRequestId = pageId + // } return { clientRequestId: input.clientRequestId,