diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 201e76621..e45789319 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -1992,7 +1992,9 @@ export enum UploadFileRequestErrorCode { } export type UploadFileRequestInput = { + clientRequestId?: InputMaybe; contentType: Scalars['String']; + createPageEntry?: InputMaybe; url: Scalars['String']; }; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index d6c81bf3e..752593100 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -1535,7 +1535,9 @@ enum UploadFileRequestErrorCode { } input UploadFileRequestInput { + clientRequestId: String contentType: String! + createPageEntry: Boolean url: String! } diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 1250a9a01..c0dde0344 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -51,6 +51,7 @@ import { stringToHash, userDataToUser, validatedDate, + titleForFilePath, } from '../../utils/helpers' import { ParsedContentPuppeteer, @@ -166,6 +167,7 @@ export const createArticleResolver = authorized< .join('.') ).replace(/_/gi, ' ') + let title: string | undefined let parsedContent: Readability.ParseResult | null = null let canonicalUrl let userArticleUrl: string | null = null @@ -219,6 +221,7 @@ export const createArticleResolver = authorized< userArticleUrl = uploadFileDetails.fileUrl canonicalUrl = uploadFile.url pageType = PageType.File + title = titleForFilePath(uploadFile.url) } else if ( source !== 'puppeteer-parse' && FORCE_PUPPETEER_URLS.some((regex) => regex.test(url)) @@ -252,6 +255,7 @@ export const createArticleResolver = authorized< content: parsedContent?.content || '', description: parsedContent?.excerpt || '', title: + title || parsedContent?.title || preparedDocument?.pageInfo.title || croppedPathname, @@ -333,7 +337,6 @@ export const createArticleResolver = authorized< } else { // update existing page's state from processing to succeeded articleToSave.archivedAt = archive ? saveTime : undefined - articleToSave.url = uploadFileUrlOverride || articleToSave.url const updated = await updatePage(pageId, articleToSave, { ...ctx, uid, diff --git a/packages/api/src/resolvers/upload_files/index.ts b/packages/api/src/resolvers/upload_files/index.ts index 4e0c4cb63..3a6a58fa5 100644 --- a/packages/api/src/resolvers/upload_files/index.ts +++ b/packages/api/src/resolvers/upload_files/index.ts @@ -16,7 +16,7 @@ import path from 'path' import normalizeUrl from 'normalize-url' import { analytics } from '../../utils/analytics' import { env } from '../../env' -import { createPage } from '../../elastic/pages' +import { createPage, getPageByParam, updatePage } from '../../elastic/pages' import { PageType } from '../../elastic/types' import { generateSlug } from '../../utils/helpers' @@ -82,28 +82,40 @@ export const uploadFileRequestResolver: ResolverFn< input.contentType ) - const pageId = await createPage( - { - id: '', - url: input.url, + if (input.createPageEntry) { + let page = await getPageByParam({ 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] } + url: input.url, + }) + if (page) { + await updatePage(page.id, { + savedAt: new Date(), + archivedAt: null, + }, ctx) + } else { + const pageId = await createPage( + { + url: input.url, + id: input.clientRequestId || '', + 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 } diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 2a0e8959a..1dc5174ea 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -416,7 +416,10 @@ const schema = gql` input UploadFileRequestInput { url: String! contentType: String! + createPageEntry: Boolean + clientRequestId: String } + enum UploadFileRequestErrorCode { UNAUTHORIZED BAD_INPUT diff --git a/packages/api/src/utils/helpers.ts b/packages/api/src/utils/helpers.ts index 73c0abdc9..8a54e3a5e 100644 --- a/packages/api/src/utils/helpers.ts +++ b/packages/api/src/utils/helpers.ts @@ -18,6 +18,8 @@ import { Merge } from '../util' import { CreateArticlesSuccessPartial } from '../resolvers' import { ArticleSavingRequestStatus, Page } from '../elastic/types' import { updatePage } from '../elastic/pages' +import path from 'path' +import normalizeUrl from 'normalize-url' interface InputObject { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -230,3 +232,13 @@ export const validatedDate = ( return undefined } } + +export const titleForFilePath = (url: string): string => { + try { + const title = decodeURI(path.basename(new URL(url).pathname, '.pdf')) + return title + } catch (e) { + console.log(e) + } + return url +} diff --git a/packages/content-fetch/fetch-content.js b/packages/content-fetch/fetch-content.js index 1c948fccc..fd9a51b56 100644 --- a/packages/content-fetch/fetch-content.js +++ b/packages/content-fetch/fetch-content.js @@ -94,7 +94,7 @@ const uploadToSignedUrl = async ({ id, uploadSignedUrl }, contentType, contentOb }) }; -const getUploadIdAndSignedUrl = async (userId, url) => { +const getUploadIdAndSignedUrl = async (userId, url, articleSavingRequestId) => { const auth = await signToken({ uid: userId }, process.env.JWT_SECRET); const data = JSON.stringify({ query: `mutation UploadFileRequest($input: UploadFileRequestInput!) { @@ -112,6 +112,7 @@ const getUploadIdAndSignedUrl = async (userId, url) => { input: { url, contentType: 'application/pdf', + clientRequestId: articleSavingRequestId, } } }); @@ -126,10 +127,10 @@ const getUploadIdAndSignedUrl = async (userId, url) => { return response.data.data.uploadFileRequest; }; -const uploadPdf = async (url, userId) => { +const uploadPdf = async (url, userId, articleSavingRequestId) => { validateUrlString(url); - const uploadResult = await getUploadIdAndSignedUrl(userId, url); + const uploadResult = await getUploadIdAndSignedUrl(userId, url, articleSavingRequestId); await uploadToSignedUrl(uploadResult, 'application/pdf', url); return uploadResult.id; }; @@ -282,7 +283,7 @@ async function fetchContent(req, res) { try { if (contentType === 'application/pdf') { - const uploadedFileId = await uploadPdf(finalUrl, userId); + const uploadedFileId = await uploadPdf(finalUrl, userId, articleSavingRequestId); const l = await saveUploadedPdf(userId, finalUrl, uploadedFileId, articleSavingRequestId); } else { if (!content || !title) {