From 57e80012aa04bad06dcd097aad0c738aabda4239 Mon Sep 17 00:00:00 2001 From: Arky Asmal Date: Sun, 5 Jan 2025 00:29:40 -0500 Subject: [PATCH] add pdf support in root of project folder name, for slides --- .../resources/projects/projectsPost/index.ts | 20 +- .../resources/utils/types/projectTypes.ts | 20 ++ .../initializeDirectoryFileHistory.ts | 30 ++- .../resources/createPDFResource.ts | 173 ++++++++++++++++++ .../googleDrive/resources/createResource.ts | 8 +- .../googleDrive/resources/modifyResources.ts | 49 +++-- .../googleDrive/resources/removeResource.ts | 58 ++++-- 7 files changed, 312 insertions(+), 46 deletions(-) create mode 100644 utils/google/googleDrive/resources/createPDFResource.ts diff --git a/app/lib/restAPI/resources/projects/projectsPost/index.ts b/app/lib/restAPI/resources/projects/projectsPost/index.ts index dfec57a..50e9708 100644 --- a/app/lib/restAPI/resources/projects/projectsPost/index.ts +++ b/app/lib/restAPI/resources/projects/projectsPost/index.ts @@ -19,15 +19,21 @@ const createDocument = (e: APIGatewayEvent) => { endDate, topics, archived, + slidesFileName, + slidesURL, + slidesGoogleResourceId, } = body; const document = { - appURL: appURL, - projectName: projectName, - githubURL: githubURL, - description: description, - endDate: endDate, - topics: topics, - archived: archived, + appURL, + projectName, + githubURL, + description, + endDate, + topics, + archived, + slidesFileName, + slidesURL, + slidesGoogleResourceId, }; return marshall(document, { convertClassInstanceToMap: true, diff --git a/app/lib/restAPI/resources/utils/types/projectTypes.ts b/app/lib/restAPI/resources/utils/types/projectTypes.ts index 4553332..32f73b2 100644 --- a/app/lib/restAPI/resources/utils/types/projectTypes.ts +++ b/app/lib/restAPI/resources/utils/types/projectTypes.ts @@ -13,6 +13,16 @@ export type Image = { googleResourceId?: string; name?: string; }; +export type PDFDocument = { + pk?: { + recordType: "projects"; + startDate: string; + dateCreated: string; + }; + googleResourceId?: string | null; + name?: string | null; + slidesURL?: string | null; +}; export type ProjectDocument = { pk: { recordType: "projects"; @@ -32,4 +42,14 @@ export type ProjectDocument = { topics?: string[]; archived?: boolean; repoOwner?: string; + slidesURL?: string | null; + slidesGoogleResourceId?: string | null; + slidesFileName?: string | null; }; +export function isPDFDocument(e: any): e is PDFDocument { + try { + return !e.slidesURL; + } catch { + return false; + } +} diff --git a/utils/google/googleDrive/initializeDirectoryFileHistory.ts b/utils/google/googleDrive/initializeDirectoryFileHistory.ts index 790c518..b1528fa 100644 --- a/utils/google/googleDrive/initializeDirectoryFileHistory.ts +++ b/utils/google/googleDrive/initializeDirectoryFileHistory.ts @@ -5,7 +5,10 @@ import { } from "../../../utils/google/googleDrive/searchForFolder"; import { ChannelDocument } from "../../../utils/google/googleDrive/watchChannels/createWatchChannel"; import { getDocuments } from "../../../utils/crudRestApiMethods/getMethod"; -import { ProjectDocument } from "../../../app/lib/restAPI/resources/utils/types/projectTypes"; +import { + PDFDocument, + ProjectDocument, +} from "../../../app/lib/restAPI/resources/utils/types/projectTypes"; import { getWatchChannels } from "../../../utils/google/googleDrive/watchChannels/getWatchChannels"; import { drive_v3 } from "googleapis"; import { Image } from "../../../app/lib/restAPI/resources/utils/types/projectTypes"; @@ -28,6 +31,10 @@ export type InitializeFileHistoryProps = { id: string; data: HobbiesDocument; } + | { + id: string; + data: PDFDocument; + } )[]; currFilesInFolder: drive_v3.Schema$File[]; }; @@ -39,8 +46,8 @@ const getHobbiesImages = async ({ }: { restApiKey: string; restApiUrl: string; - verticalStartKey?: HobbiesDocument["pk"] | null - horizontalStartKey?: HobbiesDocument["pk"] | null + verticalStartKey?: HobbiesDocument["pk"] | null; + horizontalStartKey?: HobbiesDocument["pk"] | null; }): Promise => { const defaultData = { data: { @@ -235,8 +242,25 @@ export const initializeProjectsHistory = async ({ id: channel.id, data: channel, })); + const { pk, slidesGoogleResourceId, slidesURL, slidesFileName } = + projectDocument; + const slidePDFArr: { id: string; data: PDFDocument }[] = + slidesGoogleResourceId && pk + ? [ + { + id: slidesGoogleResourceId, + data: { + pk, + googleResourceId: slidesGoogleResourceId, + slidesURL: slidesURL, + name: slidesFileName, + }, + }, + ] + : []; const prevFilesInFolder = [ ...projectDocImagesIds, + ...slidePDFArr, ...activeChannelDirectoryIds, ]; return { diff --git a/utils/google/googleDrive/resources/createPDFResource.ts b/utils/google/googleDrive/resources/createPDFResource.ts new file mode 100644 index 0000000..a0725fc --- /dev/null +++ b/utils/google/googleDrive/resources/createPDFResource.ts @@ -0,0 +1,173 @@ +import { drive_v3 } from "googleapis"; +import { searchForFileByChildResourceId } from "../searchForFolder"; +import { determineCategoryType } from "../determineCategoryType"; +import { convertToStr } from "../../../general/convertToStr"; +import { getDocuments } from "../../../crudRestApiMethods/getMethod"; +import { ProjectDocument } from "../../../../app/lib/restAPI/resources/utils/types/projectTypes"; +import { corsHeaders } from "../../../../app/lib/restAPI/resources/utils/corsLambda"; +import { uploadImgToS3 } from "../../../general/s3Actions"; +import { updateDocument } from "../../../crudRestApiMethods/postMethod"; +import { marshall } from "@aws-sdk/util-dynamodb"; + +export type CreatePDFResourceProps = { + restApiUrl: string; + apiKey: string; + bucketName: string; + drive: drive_v3.Drive; + resourceId: string; +}; +export type UploadProjectDocumentProps = { + restApiUrl: string; + apiKey: string; + bucketName: string; + fileBuffer: Buffer; + key: string; + resourceId: string; + parentName: string | undefined | null; + result: { + file: drive_v3.Schema$File; + fileBlob: Blob | null; + parents: null | drive_v3.Schema$File; + }; +}; + +const topMostDirectoryFolderName = process.env.GOOGLE_DRIVE_FOLDER_NAME; +const uploadPDFResourceItems = async ({ + restApiUrl, + apiKey, + fileKey, + resourceId, + bucketName, + fileBuffer, + addedRoute, + data, + result, +}: Pick< + UploadProjectDocumentProps, + | "restApiUrl" + | "apiKey" + | "fileBuffer" + | "bucketName" + | "resourceId" + | "result" +> & { + data: ProjectDocument; + fileKey: string; + addedRoute: string; +}) => { + const updateResult = updateDocument({ + restApiUrl, + apiKey, + data: { + key: marshall(data.pk, { + convertClassInstanceToMap: true, + removeUndefinedValues: true, + }), + slidesURL: fileKey, + slidesGoogleResourceId: resourceId, + slidesFileName: result.file.name, + }, + addedRoute: addedRoute, + }); + + //this actually uploads a file to s3, not just images + const uploadFile = uploadImgToS3({ + bucketName, + key: fileKey, + body: fileBuffer, + }); + const promiseArr = await Promise.all([uploadFile, updateResult]); + return promiseArr; +}; +const uploadToProjects = async ({ + restApiUrl, + apiKey, + parentName, + bucketName, + fileBuffer, + key, + resourceId, + result, +}: UploadProjectDocumentProps) => { + const getDocResultsPromise = getDocuments({ + restApiUrl, + apiKey, + addedRoute: "projects", + params: { + query: JSON.stringify({ + recordType: "projects", + projectName: parentName, + }), + }, + }); + const [docResults] = await Promise.all([getDocResultsPromise]); + //continue if a project doc + const docItems = docResults?.data?.result?.Items; + if (!docItems || docItems.length <= 0) + return { + statusCode: 200, + headers: corsHeaders, + message: `No relevant project found with the following name: ${parentName}`, + }; + const doc = docItems[0] as ProjectDocument; + return await uploadPDFResourceItems({ + restApiUrl, + apiKey, + bucketName, + fileBuffer: fileBuffer, + addedRoute: "projects", + fileKey: key, + resourceId, + data: doc, + result, + }); +}; + +export const createPDFResource = async ({ + restApiUrl, + apiKey, + bucketName, + drive, + resourceId, +}: CreatePDFResourceProps) => { + const result = await searchForFileByChildResourceId(drive, resourceId, true); + const parentName = result.parents?.name; + const key = `${parentName}/${resourceId}/pdf`; + const keyWithType = `${key}.${result.file.fileExtension}`; + const fileBuffer = result.fileBlob + ? Buffer.from(await result.fileBlob.arrayBuffer()) + : null; + if (!fileBuffer) return; + const category = await determineCategoryType({ + drive, + fileData: result, + topMostDirectoryFolderName: convertToStr(topMostDirectoryFolderName), + }); + let results: any; + switch (category) { + case "projects": + results = await uploadToProjects({ + restApiUrl, + apiKey, + parentName, + bucketName, + fileBuffer, + key: keyWithType, + resourceId, + result, + }); + break; + default: + return; + } + return results.map((result: any) => { + const newObj = { ...result } as any; + if (newObj["$metadata"]) delete newObj["$metadata"]; + if (newObj["$fault"]) delete newObj["$fault"]; + if (newObj.headers) delete newObj["headers"]; + if (newObj.request) delete newObj["request"]; + if (newObj.config) delete newObj["config"]; + if (newObj.request) delete newObj["request"]; + return newObj; + }); +}; diff --git a/utils/google/googleDrive/resources/createResource.ts b/utils/google/googleDrive/resources/createResource.ts index f4d476c..4f6c1a3 100644 --- a/utils/google/googleDrive/resources/createResource.ts +++ b/utils/google/googleDrive/resources/createResource.ts @@ -233,15 +233,15 @@ export const createResource = async ({ switch (category) { case "projects": results = await uploadToProjects({ + getImgDescriptionPromise, + newPlaceholderBufferPromise, + placeholderKey: placeholderKeyWithType, restApiUrl, apiKey, parentName, - getImgDescriptionPromise, - newPlaceholderBufferPromise, bucketName, fileBuffer, key: keyWithType, - placeholderKey: placeholderKeyWithType, resourceId, result, }); @@ -250,12 +250,12 @@ export const createResource = async ({ results = await uploadToHobbies({ getImgDescriptionPromise, newPlaceholderBufferPromise, + placeholderKey: placeholderKeyWithType, restApiUrl, apiKey, bucketName, fileBuffer, key: keyWithType, - placeholderKey: placeholderKeyWithType, result, resourceId, }); diff --git a/utils/google/googleDrive/resources/modifyResources.ts b/utils/google/googleDrive/resources/modifyResources.ts index 6013d9f..0905536 100644 --- a/utils/google/googleDrive/resources/modifyResources.ts +++ b/utils/google/googleDrive/resources/modifyResources.ts @@ -13,6 +13,7 @@ import { import { createResource } from "../../../../utils/google/googleDrive/resources/createResource"; import { removeResource } from "../../../../utils/google/googleDrive/resources/removeResource"; import { deleteWatchChannel } from "../../../../utils/google/googleDrive/watchChannels/deleteWatchChannel"; +import { createPDFResource } from "./createPDFResource"; export const modifyResources = async ({ resourceId, @@ -70,25 +71,35 @@ export const modifyResources = async ({ const addResourcePromise = currFilesInFolder.map((file) => { if (!file.id) return null; if (!(file.id in prevFilesMap)) { - if (file.mimeType === "application/vnd.google-apps.folder") - return createChannel({ - parentDirectoryId: resourceId, - tokenSecret, - domain: webhooksAPIDomainName, - topMostDirectoryId, - drive, - tableName: webhooksTableName, - folderId: file.id, - }); - else - return createResource({ - restApiUrl, - bucketName, - apiKey: restApiKey, - drive, - resourceId: file.id, - vision, - }); + switch (file.mimeType) { + case "application/vnd.google-apps.folder": + return createChannel({ + parentDirectoryId: resourceId, + tokenSecret, + domain: webhooksAPIDomainName, + topMostDirectoryId, + drive, + tableName: webhooksTableName, + folderId: file.id, + }); + case "application/pdf": + return createPDFResource({ + restApiUrl, + bucketName, + apiKey: restApiKey, + drive, + resourceId: file.id, + }); + default: + return createResource({ + restApiUrl, + bucketName, + apiKey: restApiKey, + drive, + resourceId: file.id, + vision, + }); + } } return null; }); diff --git a/utils/google/googleDrive/resources/removeResource.ts b/utils/google/googleDrive/resources/removeResource.ts index 6aafe62..863ac3e 100644 --- a/utils/google/googleDrive/resources/removeResource.ts +++ b/utils/google/googleDrive/resources/removeResource.ts @@ -5,8 +5,25 @@ import { searchForFileByChildResourceId } from "../searchForFolder"; import { convertToStr } from "../../../general/convertToStr"; import { drive_v3 } from "googleapis"; import { HobbiesDocument } from "../../../../app/lib/restAPI/resources/hobbies/hobbiesPut"; -import { Image } from "../../../../app/lib/restAPI/resources/utils/types/projectTypes"; +import { + Image, + isPDFDocument, + PDFDocument, +} from "../../../../app/lib/restAPI/resources/utils/types/projectTypes"; +import { updateDocument } from "../../../crudRestApiMethods/postMethod"; +import { marshall } from "@aws-sdk/util-dynamodb"; const topMostDirectoryFolderName = process.env.GOOGLE_DRIVE_FOLDER_NAME; +const cleanResult = (results: any[]) => + results.map((result) => { + const newObj = { ...result } as any; + if (newObj["$metadata"]) delete newObj["$metadata"]; + if (newObj["$fault"]) delete newObj["$fault"]; + if (newObj.headers) delete newObj["headers"]; + if (newObj.request) delete newObj["request"]; + if (newObj.config) delete newObj["config"]; + if (newObj.request) delete newObj["request"]; + return newObj; + }); export const removeResource = async ({ restApiUrl, apiKey, @@ -18,7 +35,7 @@ export const removeResource = async ({ restApiUrl: string; apiKey: string; bucketName: string; - resource: Image | HobbiesDocument; + resource: Image | HobbiesDocument | PDFDocument; }) => { if (!resource.googleResourceId) return []; const result = await searchForFileByChildResourceId( @@ -32,6 +49,26 @@ export const removeResource = async ({ }); let key: string; let addedRoute: string; + if (isPDFDocument(resource)) { + if (!resource.slidesURL || !resource.pk) return []; + const resourceDetails = updateDocument({ + restApiUrl, + apiKey, + data: { + key: marshall(resource.pk, { + convertClassInstanceToMap: true, + removeUndefinedValues: true, + }), + slidesURL: null, + slidesGoogleResourceId: null, + slidesFileName: null, + }, + addedRoute: "projects", + }); + const deleteFile = deleteImgFromS3(bucketName, resource.slidesURL); + const results = await Promise.all([deleteFile, resourceDetails]); + return cleanResult(results); + } switch (category) { case "hobbies": const hobbiesResource = resource as HobbiesDocument; @@ -65,15 +102,10 @@ export const removeResource = async ({ bucketName, resource.placeholderURL ); - const results = await Promise.all([deleteImg, deletePlaceholderImg, resourceDetails]); - return results.map((result) => { - const newObj = { ...result } as any; - if(newObj['$metadata']) delete newObj['$metadata'] - if (newObj['$fault']) delete newObj['$fault'] - if (newObj.headers) delete newObj["headers"]; - if (newObj.request) delete newObj["request"]; - if (newObj.config) delete newObj["config"]; - if (newObj.request) delete newObj["request"]; - return newObj - }) + const results = await Promise.all([ + deleteImg, + deletePlaceholderImg, + resourceDetails, + ]); + return cleanResult(results); };