UNPKG

@orvium/orvium-tools

Version:

Set of tools to interact with Orvium API

292 lines (291 loc) 15.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.importDeposit = importDeposit; /** * Importer module for managing deposits and manuscript uploads to the Orvium platform. * * This module handles the full process of importing deposit data (including author information, * metadata, keywords, community, and manuscript files) to the Orvium platform: * * - Load metadata from a local JSON file. * - Transform author data into the appropriate format for the Orvium platform. * - Create a deposit entry by sending the metadata and author information to the platform. * - Generate a signed URL for securely uploading the manuscript to a cloud storage service (e.g., AWS S3). * - Upload the manuscript file using the pre-signed URL. * - Confirm the successful upload by sending metadata to the platform. * * The module uses `dotenv` for environment variables, `axios` for HTTP requests, and `fs` and `path` * for file operations. It is designed to be used in automation processes or integrated into other * applications for deposit and manuscript management. * */ const dotenv_1 = __importDefault(require("dotenv")); const axios_1 = __importDefault(require("axios")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); /** * Load and prepare environment variables */ dotenv_1.default.config(); //Obtain the api key from the env variable const API_KEY = process.env.API_KEY; const API_KEY_USER = process.env.API_KEY_USER; // Define the base URL and the required endpoint from the environment variables const API_URL = process.env.API_URL; const DEPOSIT_IMPORT_ENDPOINT = `${API_URL}/deposits/importBasicDeposit`; const UPLOAD_ENDPOINT = `${API_URL}/deposits/:id/files`; // Check if the necessary environment variable are provided, otherwise exit if (!API_KEY || !API_KEY_USER || !API_URL) { console.error('Missing required environment variables: API_KEY, API_USER and API_URL are not set.'); process.exit(1); // Exit the process with a status code of 1 for an error } /** * Loads a JSON file from the specified file path and returns its content as an object. * This function synchronously reads the file located at the given path and parses the JSON content. * * @param {string} directoryPath - The relative or absolute path to the dir with the meta JSON file to be loaded. * @returns {Object} Returns the parsed JSON object from the file. * @throws {Error} Throws an error if the file cannot be read or if the JSON cannot be parsed. */ function loadJsonFile(directoryPath) { // Append 'meta.json' to the provided directory path const metaPath = path_1.default.join(directoryPath, 'meta.json'); try { // Reading the meta data json file const absolutePath = path_1.default.resolve(metaPath); const metaData = fs_1.default.readFileSync(absolutePath, 'utf8'); return JSON.parse(metaData); } catch (error) { console.error('Error reading JSON file:', error); throw error; } } /** * Prepares the file payload including metadata such as name, type, size, and last modification time. * This function is specifically structured to fetch file metadata synchronously. * * @param {string} manuscriptPath - The full path to the file. * @returns {object} The file payload object necessary for uploads. */ function createManuscriptMetadata(manuscriptPath) { const fileStats = fs_1.default.statSync(manuscriptPath); // Get file statistics synchronously return { file: { name: path_1.default.basename(manuscriptPath), // Extract the filename from the path type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // MIME type (adjust as necessary) size: fileStats.size, // File size in bytes lastModified: fileStats.mtimeMs // Last modified time in milliseconds } }; } /** * Transforms an author object from the input format to the desired format to be imported. * * @param {InputAuthor} inputAuthor - The author object in the input format containing the required properties. * @returns {Author} Returns a new author object with properties formatted as firstName, lastName, and orcid. * */ function transformAuthor(inputAuthor) { return { firstName: inputAuthor.first_name, lastName: inputAuthor.last_name, orcid: inputAuthor.orcid, }; } /** * Submits a deposit object to a server via a POST request and returns the unique deposit ID. * It expects the server response to include a unique identifier for the newly created deposit, which it then returns. * * @param {Deposit} deposit - The deposit object to be sent to the server. * @returns {Promise<string>} A promise that resolves with the deposit's unique identifier as a string. * @throws {Error} Throws an error if the deposit ID is not included in the server's response or if there is an issue with the HTTP request. */ function importSingleDeposit(deposit) { return __awaiter(this, void 0, void 0, function* () { var _a; try { const depositWithoutManuscript = ((_a) => { var { manuscript } = _a, rest = __rest(_a, ["manuscript"]); return rest; })(deposit); const response = yield axios_1.default.post(DEPOSIT_IMPORT_ENDPOINT, depositWithoutManuscript, { headers: { 'x-api-key': API_KEY, 'x-api-key-user': API_KEY_USER } }); if (response.data && response.data._id) { return response.data._id; // Return the deposit ID } else { throw new Error('Deposit ID not found in the response'); } } catch (error) { console.error('Error importing deposit:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message); throw error; // Properly handle the error by throwing } }); } /** * Asynchronously generates a signed URL for uploading a manuscript to S3, using manuscript metadata and deposit ID. * This function makes a POST request to a specified endpoint to obtain a signed URL, which can then be used to upload the manuscript. * * @param {string} depositId - The unique identifier for the deposit to which the manuscript will be uploaded. * @param {ManuscriptMetadata} manuscriptMetadata - The metadata for the manuscript that needs to be uploaded, including file name, type, size, etc. * @returns {Promise<string>} A promise that resolves with the signed URL needed for uploading the manuscript. * @throws {Error} Throws an error if the signed URL is not obtained from the response or if there is a network or server error. */ function generateManuscriptUploadUrl(depositId, manuscriptMetadata) { return __awaiter(this, void 0, void 0, function* () { var _a; const url = `${UPLOAD_ENDPOINT.replace(':id', depositId)}?isMainFile=true&replacePDF=false`; try { const response = yield axios_1.default.post(url, manuscriptMetadata, { headers: { 'x-api-key': API_KEY, 'x-api-key-user': API_KEY_USER // Use API key for authentication } }); if (response) { return response.data; // Return the signed URL to upload the manuscritp } else { throw new Error('Upload Signed Url not obtained'); } } catch (error) { console.error('Error getting the signed URL to upload the manuscript:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message); throw error; // Properly handle the error by throwing } }); } /** * Uploads a manuscript to an S3 bucket using a pre-signed URL obtained from a service. * This function handles the streaming upload of a manuscript file located in a specified directory * to an Amazon S3 bucket using an HTTP PUT request. The necessary headers for the request, such as * Content-Type and Content-Length, are set based on the provided manuscript metadata. * * @param {string} manuscriptPath - The manuscript path where the manuscript is stored locally. * @param {UploadSignedUrlResponse} uploadSignedUrlResponse - The response object containing the signed URL and other upload parameters. * @param {ManuscriptMetadata} manuscriptMetadata - Metadata about the manuscript, including the file size and other attributes. * @returns {Promise<AxiosResponse>} A promise that resolves with the response from the S3 service upon successful upload. * @throws {Error} An error is thrown if the upload fails, with a message detailing the cause of the failure. */ function uploadManuscriptToSignedUrl(manuscriptPath, uploadSignedUrlResponse, manuscriptMetadata) { return __awaiter(this, void 0, void 0, function* () { const manuscritpStream = fs_1.default.createReadStream(manuscriptPath); try { const response = yield axios_1.default.put(uploadSignedUrlResponse.signedUrl, manuscritpStream, { headers: { 'Content-Type': 'application/octet-stream', // Set appropriate content type 'Content-Length': manuscriptMetadata.file.size, // AWS S3 needs Content-Length set for PUT operations } }); return response; } catch (error) { console.error('Error uploading manuscript to signed URL:', error.message); throw error; // Properly handle the error by throwing } }); } /** * Confirms the upload of a manuscript by sending metadata (excluding the signed URL) to a server endpoint. * This function makes an HTTP PATCH request to the server to confirm that a manuscript has been successfully * uploaded to the designated storage based on the provided deposit ID. The function uses part of the upload * response (metadata without the signed URL) to make this confirmation. * * @param {string} depositId - The unique identifier for the deposit to which the manuscript has been uploaded. * @param {UploadSignedUrlResponse} uploadSignedUrlResponse - The response object from the upload attempt, * containing the signed URL and other manuscript metadata. * @returns {Promise<AxiosResponse>} A promise that resolves with the server's response to the confirmation request. * @throws {Error} Throws an error if the confirmation fails, with a message detailing the cause of the failure. */ function confirmManuscriptImported(depositId, uploadSignedUrlResponse) { return __awaiter(this, void 0, void 0, function* () { const confirm_url = `${API_URL}/deposits/${depositId}/files/confirm`; const { signedUrl } = uploadSignedUrlResponse, manuscriptImportedMetadata = __rest(uploadSignedUrlResponse, ["signedUrl"]); try { const confirmResponse = yield axios_1.default.patch(confirm_url, manuscriptImportedMetadata, { headers: { 'x-api-key': API_KEY, // Use API key for authentication 'x-api-key-user': API_KEY_USER } }); return confirmResponse; } catch (error) { console.error('Error confirming the manuscript upload:', error.message); throw error; // Properly handle the error by throwing } }); } /** * Handles the full process of importing a deposit based on metadata loaded from a specified directory. * This function orchestrates several steps to fully integrate a manuscript into the system: it loads metadata, * creates a deposit, uploads the manuscript to a pre-signed URL, and finally confirms the manuscript upload. * * @param directoryPath - Directory with the deposit manuscript and metadata * @param community - Orvium community where the deposit will be uploaded * @returns {Promise<void>} - A promise that resolves if the entire deposit process completes successfully. * @throws {Error} - Throws an error if any step in the deposit, upload, or confirmation process fails. */ function importDeposit(directoryPath, community) { return __awaiter(this, void 0, void 0, function* () { try { const metaData = loadJsonFile(directoryPath); const deposit = { title: metaData.title, abstract: metaData.abstract, community: community, authors: metaData.authors.map(transformAuthor), disciplines: metaData.disciplines, keywords: metaData.keywords, manuscript: { filename: metaData.manuscript.filename, } }; //Extract the manuscript name and type from metadata.json const manuscriptName = deposit.manuscript.filename; // Import the deposit and get the deposit ID const depositId = yield importSingleDeposit(deposit); // Getting SignedUrl const manuscriptPath = path_1.default.join(directoryPath, manuscriptName); const manuscriptMetadata = yield createManuscriptMetadata(manuscriptPath); const uploadSignedUrlResponse = yield generateManuscriptUploadUrl(depositId, manuscriptMetadata); // Upload to signedUrl const uploadStatus = yield uploadManuscriptToSignedUrl(manuscriptPath, uploadSignedUrlResponse, manuscriptMetadata); // Confirm and change metadata yield confirmManuscriptImported(depositId, uploadSignedUrlResponse); console.log('Deposit imported Successfully: ', deposit.title); } catch (error) { console.error('An error occurred during import or upload:', error); } }); }