diff --git a/server/endpoints/api/document/index.js b/server/endpoints/api/document/index.js index f49cf0dd436e692f3a27da3791afd15fcade2e84..014784fe464f3aa9753e69975a162f7ddb91340f 100644 --- a/server/endpoints/api/document/index.js +++ b/server/endpoints/api/document/index.js @@ -4,6 +4,7 @@ const { handleAPIFileUpload } = require("../../../utils/files/multer"); const { viewLocalFiles, findDocumentInDocuments, + getDocumentsByFolder, normalizePath, isWithin, } = require("../../../utils/files"); @@ -395,6 +396,59 @@ function apiDocumentEndpoints(app) { } }); + app.get( + "/v1/documents/folder/:folderName", + [validApiKey], + async (request, response) => { + /* + #swagger.tags = ['Documents'] + #swagger.description = 'Get all documents stored in a specific folder.' + #swagger.parameters['folderName'] = { + in: 'path', + description: 'Name of the folder to retrieve documents from', + required: true, + type: 'string' + } + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + type: 'object', + example: { + folder: "custom-documents", + documents: [ + { + name: "document1.json", + type: "file", + cached: false, + pinnedWorkspaces: [], + watched: false, + // ... other document metadata + }, + // more documents + ] + } + } + } + } + } + #swagger.responses[403] = { + schema: { + "$ref": "#/definitions/InvalidAPIKey" + } + } + */ + try { + const { folderName } = request.params; + const result = await getDocumentsByFolder(folderName); + response.status(200).json(result); + } catch (e) { + console.error(e.message, e); + response.sendStatus(500).end(); + } + } + ); + app.get( "/v1/document/accepted-file-types", [validApiKey], diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index 7a459c9cfa71566fea6475cdf43da0120254e262..3c52dc16444a9e30de0d826cc1d88f4751c19768 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -1124,6 +1124,30 @@ } } }, + "/v1/documents/folder/{folderName}": { + "get": { + "tags": [ + "Documents" + ], + "description": "Get all documents stored in a specific folder.", + "parameters": [ + { + "name": "folderName", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Name of the folder to retrieve documents from" + } + ], + "responses": { + "403": { + "description": "Forbidden" + } + } + } + }, "/v1/document/accepted-file-types": { "get": { "tags": [ diff --git a/server/utils/files/index.js b/server/utils/files/index.js index 625d8582cdef7b37009169a890ce852f10ef440f..4b33fbc0c8215aff9473e147d19359e66bc17cb8 100644 --- a/server/utils/files/index.js +++ b/server/utils/files/index.js @@ -91,6 +91,50 @@ async function viewLocalFiles() { return directory; } +async function getDocumentsByFolder(folderName = "") { + if (!folderName) throw new Error("Folder name must be provided."); + const folderPath = path.resolve(documentsPath, normalizePath(folderName)); + if ( + !isWithin(documentsPath, folderPath) || + !fs.existsSync(folderPath) || + !fs.lstatSync(folderPath).isDirectory() + ) + throw new Error(`Folder "${folderName}" does not exist.`); + + const documents = []; + const filenames = {}; + const files = fs.readdirSync(folderPath); + for (const file of files) { + if (path.extname(file) !== ".json") continue; + const filePath = path.join(folderPath, file); + const rawData = fs.readFileSync(filePath, "utf8"); + const cachefilename = `${folderName}/${file}`; + const { pageContent, ...metadata } = JSON.parse(rawData); + documents.push({ + name: file, + type: "file", + ...metadata, + cached: await cachedVectorInformation(cachefilename, true), + }); + filenames[cachefilename] = file; + } + + // Get pinned and watched information for each document in the folder + const pinnedWorkspacesByDocument = + await getPinnedWorkspacesByDocument(filenames); + const watchedDocumentsFilenames = + await getWatchedDocumentFilenames(filenames); + for (let doc of documents) { + doc.pinnedWorkspaces = pinnedWorkspacesByDocument[doc.name] || []; + doc.watched = Object.prototype.hasOwnProperty.call( + watchedDocumentsFilenames, + doc.name + ); + } + + return { folder: folderName, documents }; +} + /** * Searches the vector-cache folder for existing information so we dont have to re-embed a * document and can instead push directly to vector db. @@ -304,4 +348,5 @@ module.exports = { documentsPath, hasVectorCachedFiles, purgeEntireVectorCache, + getDocumentsByFolder, };