From 928d743cb5cd2e5687856586d192e024c829e0d0 Mon Sep 17 00:00:00 2001 From: mihay42 Date: Fri, 12 Jan 2024 21:11:18 -0800 Subject: [PATCH 1/2] Bugfixes, safety features, cleanups & refinements --- cli/mrcli-company.js | 37 +++++++++++--- cli/mrcli-interaction.js | 20 ++++++++ cli/mrcli-setup.js | 90 ++++++---------------------------- cli/mrcli-study.js | 4 ++ cli/mrcli.js | 3 +- package-lock.json | 7 +-- package.json | 5 +- src/api/authorize.js | 2 +- src/api/gitHubServer.js | 61 ++++++++++++++++++++++- src/api/github.js | 20 +++++--- src/cli/companyWizard.js | 76 ++++++++++++++++------------ src/cli/filesystem.js | 2 +- src/cli/installInstructions.js | 4 +- 13 files changed, 195 insertions(+), 136 deletions(-) diff --git a/cli/mrcli-company.js b/cli/mrcli-company.js index 4ab8d86..3143a63 100755 --- a/cli/mrcli-company.js +++ b/cli/mrcli-company.js @@ -11,7 +11,8 @@ // Import required modules import { CompanyStandalone } from '../src/report/companies.js' -import { Companies, Studies } from '../src/api/gitHubServer.js' +import { Interactions, Companies, Studies, Users } from '../src/api/gitHubServer.js' +import GitHubFunctions from '../src/api/github.js' import AddCompany from '../src/cli/companyWizard.js' import Environmentals from '../src/cli/env.js' import CLIOutput from '../src/cli/output.js' @@ -46,6 +47,7 @@ myArgs = myArgs.opts() // Read the environmental settings const myConfig = environment.readConfig(myArgs.conf_file) let myEnv = environment.getEnv(myArgs, myConfig) +myEnv.company = 'Unknown' const accessToken = await environment.verifyAccessToken() const processName = 'mrcli-company' @@ -57,10 +59,11 @@ const wutils = new WizardUtils() // Construct the controller objects const companyCtl = new Companies(accessToken, myEnv.gitHubOrg, processName) -// const studyCtl = new Studies(accessToken, myEnv.gitHubOrg, processName) +const interactionCtl = new Interactions(accessToken, myEnv.gitHubOrg, processName) +const gitHubCtl = new GitHubFunctions(accessToken, myEnv.gitHubOrg, processName) -// TODO: We need to create a higher level abstraction for capturing the owning company -// const owningCompany = await serverOps.getOwningCompany(companyCtl) +// const studyCtl = new Studies(accessToken, myEnv.gitHubOrg, processName) +const userCtl = new Users(accessToken, myEnv.gitHubOrg, processName) // Predefine the results variable let [success, stat, results] = [null, null, null] @@ -211,20 +214,30 @@ if (myArgs.report) { stat = foundObjects[1] results = foundObjects[2] } else if (myArgs.update) { + const lockResp = companyCtl.checkForLock() + if(lockResp[0]) { + console.log(`ERROR: ${lockResp[1].status_msg}`) + process.exit(-1) + } const myCLIObj = JSON.parse(myArgs.update) - const mySpinner = new ora(`Updating company [${myCLIObj.name}] object ...`) + const mySpinner = new ora(`Updating company [${myCLIObj.name}] ...`) mySpinner.start() const [success, stat, resp] = await companyCtl.updateObj(myCLIObj) mySpinner.stop() if(success) { console.log(`SUCCESS: ${stat.status_msg}`) process.exit(0) - } else { + } else { console.log(`ERROR: ${stat.status_msg}`) process.exit(-1) } // TODO: Need to reimplement the below to account for GitHub } else if (myArgs.delete) { + const lockResp = companyCtl.checkForLock() + if(lockResp[0]) { + console.log(`ERROR: ${lockResp[1].status_msg}`) + process.exit(-1) + } // Use operationOrNot to confirm the delete const deleteOrNot = await wutils.operationOrNot(`Preparing to delete the company [${myArgs.delete}], are you sure?`) if(!deleteOrNot) { @@ -248,8 +261,13 @@ if (myArgs.report) { process.exit(-1) } } else if (myArgs.add_wizard) { + const lockResp = companyCtl.checkForLock() + if(lockResp[0]) { + console.log(`ERROR: ${lockResp[1].status_msg}`) + process.exit(-1) + } myEnv.DEFAULT = {company: 'Unknown'} - const newCompany = new AddCompany(myEnv, companyCtl) + const newCompany = new AddCompany(myEnv, {github: gitHubCtl, interaction: interactionCtl, company: companyCtl, user: userCtl}) const result = await newCompany.wizard() if(result[0]) { console.log('SUCCESS: Created new company in the backend') @@ -261,6 +279,11 @@ if (myArgs.report) { } else if (myArgs.reset_by_type) { console.error(`WARNING: CLI function not yet implemented for companies: %d`, -1) process.exit(-1) + const lockResp = companyCtl.checkForLock() + if(lockResp[0]) { + console.log(`ERROR: ${lockResp[1].status_msg}`) + process.exit(-1) + } // TODO: Need to reimplement the below to account for GitHub, and this is where we will start to use the new CLIOutput } else { [success, stat, results] = await companyCtl.getAll() diff --git a/cli/mrcli-interaction.js b/cli/mrcli-interaction.js index ddcef19..778faee 100755 --- a/cli/mrcli-interaction.js +++ b/cli/mrcli-interaction.js @@ -184,6 +184,11 @@ if (myArgs.report) { stat = foundObjects[1] results = foundObjects[2] } else if (myArgs.update) { + const lockResp = interactionCtl.checkForLock() + if(lockResp[0]) { + console.log(`ERROR: ${lockResp[1].status_msg}`) + process.exit(-1) + } const myCLIObj = JSON.parse(myArgs.update) const mySpinner = new ora(`Updating interaction [${myCLIObj.name}] object ...`) mySpinner.start() @@ -197,6 +202,11 @@ if (myArgs.report) { process.exit(-1) } } else if (myArgs.delete) { + const lockResp = interactionCtl.checkForLock() + if(lockResp[0]) { + console.log(`ERROR: ${lockResp[1].status_msg}`) + process.exit(-1) + } // Use operationOrNot to confirm the delete const deleteOrNot = await wutils.operationOrNot(`Preparing to delete the interaction [${myArgs.delete}], are you sure?`) if(!deleteOrNot) { @@ -216,6 +226,11 @@ if (myArgs.report) { process.exit(-1) } } else if (myArgs.add_wizard) { + const lockResp = interactionCtl.checkForLock() + if(lockResp[0]) { + console.log(`ERROR: ${lockResp[1].status_msg}`) + process.exit(-1) + } const newInteraction = new AddInteraction(myEnv, {github: gitHubCtl, interaction: interactionCtl, company: companyCtl, user: userCtl}) const result = await newInteraction.wizard() if(result[0]) { @@ -228,6 +243,11 @@ if (myArgs.report) { } else if (myArgs.reset_by_type) { console.error('ERROR (%d): Reset by type not implemented.', -1) process.exit(-1) + const lockResp = interactionCtl.checkForLock() + if(lockResp[0]) { + console.log(`ERROR: ${lockResp[1].status_msg}`) + process.exit(-1) + } const resetResponses = await resetStatuses(myArgs.reset_by_type, interactionCtl) if(resetResponses[0]) { console.log(`SUCCESS: Reset status of ${resetResponses[2].successful.length} interactions.`) diff --git a/cli/mrcli-setup.js b/cli/mrcli-setup.js index 02095c7..3445431 100755 --- a/cli/mrcli-setup.js +++ b/cli/mrcli-setup.js @@ -25,7 +25,7 @@ import inquirer from "inquirer" import Environmentals from '../src/cli/env.js' import { GitHubAuth } from '../src/api/authorize.js' -import { Companies } from '../src/api/gitHubServer.js' +import { Companies, Users } from '../src/api/gitHubServer.js' import GitHubFunctions from "../src/api/github.js" import Table from 'cli-table' import ora from "ora" @@ -96,9 +96,8 @@ async function simplePrompt(message) { function printNextSteps() { // Print out the next steps console.log(`Now that you\'ve performed the initial registration here\'s what\'s next.`) - console.log(chalk.blue.bold(`\t1. Create and register additional companies with mrcli company --add_wizard.`)) - console.log(chalk.blue.bold(`\t2. Register and add interactions with mrcli interaction --add_wizard.`)) - console.log('\nWith additional companies and new interactions registered the mediumroast.io caffeine\nservice will perform basic company comparisons.') + console.log(chalk.blue.bold(`\t1. Create and register additional companies with \'mrcli company --add_wizard\'.`)) + console.log(chalk.blue.bold(`\t2. Register and add interactions with \'mrcli interaction --add_wizard\'.`)) cliOutput.printLine() } @@ -298,6 +297,7 @@ cliOutput.printLine() let prevInstall = false // Construct the controller objects const companyCtl = new Companies(myConfig.GitHub.token, myConfig.GitHub.org, `mrcli-setup`) +const userCtl = new Users(myConfig.GitHub.token, myConfig.GitHub.org, `mrcli-setup`) // const studyCtl = new Studies(myConfig.GitHub.token, myConfig.GitHub.org, `mrcli-setup`) // Check to see if the company and study objects exist @@ -334,8 +334,8 @@ if(!configExists[0]) { // Confirm that Document directory exists and if not create it const docDir = myConfig.DEFAULT.report_output_dir const reportDirExists = fsUtils.safeMakedir(docDir) -if(reportDirExists[0]) { - console.log(chalk.bold.yellow(`WARNING: Report output directory [${docDir}] not detected, created.`)) +if(!reportDirExists[0]) { + console.log(chalk.bold.red(`ERROR: Unable to create report directory [${docDir}].`)) } /* --------- End save config file ---------- */ @@ -390,17 +390,13 @@ cliOutput.printLine() /* ----------------------------------------- */ /* ---- Begin initial objects creation ----- */ -// Create needed objects -let companies = [] -// let studies = [] - // Create the owning company console.log(chalk.blue.bold('Creating your owning company')) -myConfig.DEFAULT.company = myConfig.GitHub.org +myConfig.company = myConfig.GitHub.org myEnv.splash = false const cWizard = new AddCompany( myConfig, - companyCtl, + {github: gitHubCtl, interaction: null, company: companyCtl, user: userCtl}, myConfig.DEFAULT.company_dns ) const owningCompanyResp = await cWizard.wizard(true, false) @@ -408,91 +404,37 @@ let owningCompany = owningCompanyResp[2] // Create the first company // Reset company user name to user name set in the company wizard -myConfig.DEFAULT.company = 'Unknown' +myConfig.company = 'Unknown' const firstComp = new AddCompany( myConfig, - companyCtl, // NOTE: Company creation is commented out + {github: gitHubCtl, interaction: null, company: companyCtl, user: userCtl}, myConfig.DEFAULT.company_dns ) console.log(chalk.blue.bold('Creating the first company ...')) let firstCompanyResp = await firstComp.wizard(false, false) const firstCompany = firstCompanyResp[2] -// NOTE: For the first release studies aren't needed, therefore we're commenting out anything related to them -// const linkedCompanies = companyCtl.linkObj([owningCompany, firstCompany]) - -// Create a default study for good housekeeping -process.stdout.write(chalk.blue.bold(`Creating default study ... `)) -let myStudy = { - name: 'Default Study', - description: 'A placeholder study to ensure that interactions are able to have something to link to', - public: false, - groups: 'default:default', - document: {}, - linked_companies: linkedCompanies, - linked_interactions: {} -} -console.log(chalk.bold.green('Ok')) - -// NOTE: Since studies aren't needed in the alpha_2 series of releases we will comment things out related to them. -// Additionally, in alpha_3 we'll determine if we need to create a default study or not. So leaving this -// code in place for now. -// Obtain the link object for studies -// const linkedStudies = studyCtl.linkObj([myStudy]) -// const linkedStudies = {} - -// Link the study to the companies -// owningCompany.linked_studies = linkedStudies -// firstCompany.linked_studies = linkedStudies -// companies = [owningCompany, firstCompany] - // Set up the spinner let spinner // Save the companies to GitHub spinner = ora(chalk.bold.blue('Saving companies to GitHub ... ')) spinner.start() // Start the spinner - const companyResp = await companyCtl.createObj(companies) + const companyResp = await companyCtl.createObj2([owningCompany, firstCompany]) spinner.stop() // Stop the spinner // If the company creation failed then exit if(!companyResp[0]) { - console.log(chalk.red.bold(`Failed to create companies, exiting with: [${companyResp[1]}], you may need to clean up the repo.`)) + console.log(chalk.red.bold(`FAILED: ${companyResp[1].status_msg}, you may need to clean up the repo.`)) process.exit(-1) -} else { - console.log(chalk.bold.green('\tCompanies saved to GitHub.')) -} - -// Save the default study to GitHub -// spinner = ora(chalk.bold.blue('Saving study to GitHub ... ')) -// spinner.start() // Start the spinner -// const studyResp = await studyCtl.createObj([myStudy]) -// spinner.stop() // Stop the spinner -// // If the study creation failed then exit -// if(!studyResp[0]) { -// console.log(chalk.red.bold(`Failed to create study, exiting with: [${studyResp[1]}], you may need to clean up the repo.`)) -// process.exit(-1) -// } else { -// console.log(chalk.bold.green('\tDefault study saved to GitHub.')) -// } - +} cliOutput.printLine() + /* ------ End initial objects creation ----- */ /* ----------------------------------------- */ -// List all created objects to the console -let results - -// Studies output -console.log(chalk.blue.bold(`Fetching and listing all created objects`)) -cliOutput.printLine() -// console.log(chalk.blue.bold(`Default study:`)) -// results = await studyCtl.getAll() -// cliOutput.outputCLI(results[2].mrJson) -// cliOutput.printLine() - // Companies output -console.log(chalk.blue.bold(`Owning and first companies:`)) -results = await companyCtl.getAll() +console.log(chalk.blue.bold(`Fetching and listing Owning and first companies:`)) +const results = await companyCtl.getAll() cliOutput.outputCLI(results[2].mrJson) cliOutput.printLine() cliOutput.printLine() diff --git a/cli/mrcli-study.js b/cli/mrcli-study.js index 5e7c9b8..2a52008 100755 --- a/cli/mrcli-study.js +++ b/cli/mrcli-study.js @@ -16,6 +16,10 @@ import { Studies } from '../src/api/gitHubServer.js' // import AddStudy from '../src/cli/studyWizard.js' import Environmentals from '../src/cli/env.js' import CLIOutput from '../src/cli/output.js' +import chalk from 'chalk' + +console.log(chalk.bold.yellow('NOTICE: Studies aren\'t supported in this release, exiting.')) +process.exit() // Globals const objectType = 'Studies' diff --git a/cli/mrcli.js b/cli/mrcli.js index b5fde62..3d18cb8 100755 --- a/cli/mrcli.js +++ b/cli/mrcli.js @@ -14,13 +14,12 @@ import program from 'commander' program .name('mrcli') - .version('0.7.0') + .version('0.4.34') .description('mediumroast.io command line interface') .command('setup', 'setup the mediumroast.io system via the command line').alias('f') .command('interaction', 'manage and report on mediumroast.io interaction objects').alias('i') .command('company', 'manage and report on mediumroast.io company objects').alias('c') .command('study', 'manage and report on mediumroast.io study objects').alias('s') .command('user', 'manage and report on mediumroast.io users').alias('u') - .command('github', 'test code to interoperate with GitHub').alias('g') program.parse(process.argv) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3d013aa..a8a7714 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mediumroast_js", - "version": "0.4.17", + "version": "0.4.31", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mediumroast_js", - "version": "0.4.17", + "version": "0.4.31", "license": "Apache-2.0", "dependencies": { "@json2csv/plainjs": "^7.0.4", @@ -16,7 +16,6 @@ "box-plot": "^1.0.0", "cli-progress": "^3.11.2", "cli-table": "^0.3.11", - "cli-truncate": "^3.1.0", "commander": "^8.3.0", "configparser": "^0.3.9", "docx": "^7.4.1", @@ -27,8 +26,6 @@ "octokit": "^3.1.2", "open": "^9.1.0", "ora": "^6.1.2", - "prompt-sync": "^4.2.0", - "terminal-link": "^3.0.0", "uninstall": "^0.0.0", "wrap-ansi": "^8.1.0", "xlsx": "^0.18.5" diff --git a/package.json b/package.json index 6d49530..dd406e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mediumroast_js", - "version": "0.4.25", + "version": "0.4.34", "description": "A Command Line Interface (CLI) and Javascript SDK to interact with mediumroast.io.", "main": "cli/mrcli.js", "scripts": { @@ -63,7 +63,6 @@ "box-plot": "^1.0.0", "cli-progress": "^3.11.2", "cli-table": "^0.3.11", - "cli-truncate": "^3.1.0", "commander": "^8.3.0", "configparser": "^0.3.9", "docx": "^7.4.1", @@ -74,8 +73,6 @@ "octokit": "^3.1.2", "open": "^9.1.0", "ora": "^6.1.2", - "prompt-sync": "^4.2.0", - "terminal-link": "^3.0.0", "uninstall": "^0.0.0", "wrap-ansi": "^8.1.0", "xlsx": "^0.18.5" diff --git a/src/api/authorize.js b/src/api/authorize.js index de734d9..3d08c69 100644 --- a/src/api/authorize.js +++ b/src/api/authorize.js @@ -229,7 +229,7 @@ class GitHubAuth { deviceCode = verifier.device_code // Print the verification artifact to the console console.log( - chalk.blue.bold(`If your OS supports it, opening your browser, otherwise, navigate to the website below.\n`) + chalk.blue.bold(`If your OS supports it, opening your browser, otherwise, navigate to the Authorization website. Then, please copy and paste the Authorization code into your browser.\n`) ) const table = new Table({ rows: [ diff --git a/src/api/gitHubServer.js b/src/api/gitHubServer.js index bcf19ef..58cd81b 100644 --- a/src/api/gitHubServer.js +++ b/src/api/gitHubServer.js @@ -27,6 +27,12 @@ class baseObjects { constructor(token, org, processName, objType) { this.serverCtl = new GitHubFunctions(token, org, processName) this.objType = objType + this.objectFiles = { + Studies: 'Studies.json', + Companies: 'Companies.json', + Interactions: 'Interactions.json', + Users: null + } } /** @@ -104,6 +110,53 @@ class baseObjects { async createObj(objs) { return await this.serverCtl.createObjects(this.objType, objs) } + + async createObj2(objs) { + // Create the repoMetadata object + let repoMetadata = { + containers: { + [this.objType]: {} + }, + branch: {} + } + // Catch the container + const caught = await this.serverCtl.catchContainer(repoMetadata) + // If the container is locked then return the caught object + if(!caught[0]) { + return caught + } + // Get the sha for the current branch/object + const sha = await this.serverCtl.getSha( + this.objType, + this.objectFiles[this.objType], + repoMetadata.branch.name + ) + // If the sha is not found then return the sha object + if(!sha[0]) { + return sha + } + // Append the new object to the existing objects + const mergedObjects = [...caught[2].containers[this.objType].objects, ...objs] + // Write the new objects to the container + const writeResp = await this.serverCtl.writeObject( + this.objType, + mergedObjects, + repoMetadata.branch.name, + sha[2] + ) + // If the write fails then return the writeResp + if(!writeResp[0]) { + return writeResp + } + // Release the container + const released = await this.serverCtl.releaseContainer(caught[2]) + // If the release fails then return the released object + if(!released[0]) { + return released + } + // Return a success message + return [true, {status_code: 200, status_msg: `created [${objs.length}] ${this.objType}`}, null] + } /** * @async @@ -146,6 +199,12 @@ class baseObjects { } return linkedObjs } + + // Create a function that checks for a locked container using the serverCtl.checkForLock() function + async checkForLock() { + return await this.serverCtl.checkForLock(this.objType) + } + } class Studies extends baseObjects { @@ -220,7 +279,7 @@ class Companies extends baseObjects { const { name, key, value } = objToUpdate // Define the attributes that can be updated by the user const whiteList = [ - 'description', 'company_type', 'url', 'role', 'wikipedia_url', 'status', + 'description', 'company_type', 'url', 'role', 'wikipedia_url', 'status', 'logo_url', 'region', 'country', 'city', 'state_province', 'zip_postal', 'street_address', 'latitude', 'longitude','phone', 'google_maps_url', 'google_news_url', 'google_finance_url','google_patents_url', diff --git a/src/api/github.js b/src/api/github.js index 9c80fa9..4ce2a0c 100644 --- a/src/api/github.js +++ b/src/api/github.js @@ -38,9 +38,10 @@ class GitHubFunctions { constructor (token, org, processName) { this.token = token this.orgName = org - this.repoName = `${org}_mediumroast_app_repo` + this.repoName = `${org}_discovery` this.repoDesc = `A repository for all of the mediumroast.io application assets.` this.octCtl = new Octokit({auth: token}) + // NOTE: The lockfile name needs to be more flexible in checking for the lockfile this.lockFileName = `${processName}.lock` this.mainBranchName = 'main' this.objectFiles = { @@ -71,7 +72,7 @@ class GitHubFunctions { }) return [true, {status_code:200, status_msg: `captured sha for [${containerName}/${fileName}]`}, response.data.sha] } catch (err) { - return [false, {status_code: 500, status_msg: `unable to capture sha for [${containerName}/${fileName}] due to [${err}]`}, err.message] + return [false, {status_code: 500, status_msg: `unable to capture sha for [${containerName}/${fileName}] due to [${err.message}]`}, err] } } @@ -282,14 +283,17 @@ class GitHubFunctions { path: containerName }) + // Can we search for a file with an extension of .lock? + // This is due to the fact that there are other processes that may create lock files. + const lockExists = mainContents.data.some( item => item.path === `${containerName}/${this.lockFileName}` ) if (lockExists) { - return [true, `SUCCESS: container [${containerName}] is locked with lock file [${this.lockFileName}]`] + return [true, {status_code: 200, status_msg: `container [${containerName}] is locked with lock file [${this.lockFileName}]`}, lockExists] } else { - return [false, `FAILED: container [${containerName}] is not locked with lock file [${this.lockFileName}]`] + return [false, {status_code: 404, status_msg: `container [${containerName}] is not locked with lock file [${this.lockFileName}]`}, lockExists] } } @@ -784,7 +788,7 @@ class GitHubFunctions { // Call the method above to check for a lock const lockExists = await this.checkForLock(container) // If the lock exists return an error - if(lockExists[0]) { return [false, `ERROR: The container [${container}] is locked unable to create objects.`] } + if(lockExists[0]) { return [false, {status_code: 503, status_msg:`the container [${container}] is locked unable and cannot perform creates, updates or deletes on objects.`}, lockExists] } } // Lock the containers @@ -792,7 +796,7 @@ class GitHubFunctions { // Call the method above to lock the container const locked = await this.lockContainer(container) // Check to see if the container was locked and return the error if not - if(!locked[0]) { return [false, `ERROR: Unable to lock [${container}] cannot create new objects.`, locked] } + if(!locked[0]) { return [false, {status_code: 503, status_msg: `unable to lock [${container}] and cannot perform creates, updates or deletes on objects.`}, locked] } // Save the lock sha repoMetadata.containers[container].lockSha = locked[2].data.content.sha } @@ -801,7 +805,7 @@ class GitHubFunctions { // Call the method above createBranchFromMain to create a new branch const branchCreated = await this.createBranchFromMain() // Check to see if the branch was created - if(!branchCreated[0]) { return [false, `ERROR: Unable to create new branch`, branchCreated] } + if(!branchCreated[0]) { return [false, {status_code: 503, status_msg: `unable to create new branch`}, branchCreated] } // Save the branch sha into containers as a separate object repoMetadata.branch = { name: branchCreated[2].data.ref, @@ -820,7 +824,7 @@ class GitHubFunctions { repoMetadata.containers[container].objects = readResponse[2].mrJson } - return [true,`SUCCESS: ${repoMetadata.containers.length} containers are ready for use.`, repoMetadata] + return [true,{status_code: 200, status_msg: `${repoMetadata.containers.length} containers are ready for use.`}, repoMetadata] } diff --git a/src/cli/companyWizard.js b/src/cli/companyWizard.js index 5144888..37f82fd 100755 --- a/src/cli/companyWizard.js +++ b/src/cli/companyWizard.js @@ -17,6 +17,7 @@ import CLIOutput from "./output.js" import crypto from "node:crypto" import axios from "axios" + class AddCompany { /** * A class which encodes the steps needed to create a company in the mediumroast.io application. There are two @@ -35,9 +36,12 @@ class AddCompany { * @todo There is a bug in how the initial company object is created, specifically the name of the company isn't set correctly * @todo When polling for the GitHub organization check to see what other metadata could be useful like desc, website, etc. */ - constructor(env, apiController, companyDNSUrl=null, companyLogoUrl=null, nominatimUrl=null){ + constructor(env, apiControllers, companyDNSUrl=null, companyLogoUrl=null, nominatimUrl=null){ this.env = env - this.apiController = apiController + this.apiController = apiControllers.company + this.interactionCtl = apiControllers.interaction + this.userCtl = apiControllers.user + this.githubCtl = apiControllers.github this.endpoint = "/V2.0/company/merged/firmographics/" this.sicEndpoint = "/V2.0/sic/description/" @@ -60,15 +64,6 @@ class AddCompany { } this.companyLogosRest = new mrRest(this.companyLogosCred) - this.env.nominatim ? this.nominatim = this.env.nominatim : this.nominatim = nominatimUrl - this.nominatimCred = this.companyDNSCred = { - apiKey: "Not Applicable", - restServer: this.nominatim, - user: "Not Applicable", - secret: "Not Applicable" - } - this.nominatiumRest = new mrRest(this.nominatimCred) - // Splash screen elements this.name = "mediumroast.io Company Wizard" this.version = "version 2.0.0" @@ -82,16 +77,16 @@ class AddCompany { } // TODO we will deprecate this operation in favor of linking in the backend - _linkObj(name) { - // Hash the names - // const intHash = this.crypt.createHash('sha256', prototype.name.value).digest('hex') - const objHash = crypto.createHash('sha256', name).digest('hex') - - // Create the object Link - let objLink = {} - objLink[name] = objHash - return objLink - } + // _linkObj(name) { + // // Hash the names + // // const intHash = this.crypt.createHash('sha256', prototype.name.value).digest('hex') + // const objHash = crypto.createHash('sha256', name).digest('hex') + + // // Create the object Link + // let objLink = {} + // objLink[name] = objHash + // return objLink + // } async getCompany (companyName) { let myCompany = {} @@ -147,16 +142,20 @@ class AddCompany { } async getLogo (companyWebsite) { - const myLogos = await this.companyLogosRest.getObj(companyWebsite) - return myLogos[2].icons[0].url + try { + const myLogos = await this.companyLogosRest.getObj(companyWebsite) + return myLogos[2].icons[0].url + } catch (err) { + return null + } } async getLatLong(address) { const response = await axios.get(`https://nominatim.openstreetmap.org/search?q=${address}&format=json`) if (response.data && response.data[0]) { - return [true, {status_code: 200, status_msg: `SUCCESS: found coordinates for ${address}`}, [parseFloat(response.data[0].lat), parseFloat(response.data[0].lon)]] + return [true, {status_code: 200, status_msg: `found coordinates for ${address}`}, [parseFloat(response.data[0].lat), parseFloat(response.data[0].lon)]] } else { - return [false, {status_code: 404, status_msg: `FAILED: could not find coordinates for ${address}`}, null] + return [false, {status_code: 404, status_msg: `could not find coordinates for ${address}`}, null] } } @@ -636,15 +635,23 @@ class AddCompany { ) } + // Capture the current user + const myUserResp = await this.userCtl.getMyself() + const myUser = myUserResp[2] + // Set the prototype object which can be used for creating a real object. // Since the backend expects certain attributes that may not be human readable, the // prototype below contains strings that are easier to read. Additionally, should // we wish to set some defaults for each one it is also feasible within this // prototype object to do so. let companyPrototype = { - name: {consoleString: "name", value:this.env.DEFAULT.company}, + name: {consoleString: "name", value:this.env.company}, description: {consoleString: "description", value:this.defaultValue}, role: {consoleString: "role (e.g. Owner, Competitor, Partner, etc.)", value:this.defaultValue}, + organization: {consoleString: "", value: this.env.gitHubOrg}, // Set the organization to the GitHub organization + creator: {consoleString: "", value: myUser.login}, // Set the creator to the GitHub user + creator_id: {consoleString: "", value: myUser.id}, // Set the creator to the GitHub user + creator_name: {consoleString: "", value: myUser.name}, // Set the creator to the GitHub user region: {consoleString: "region (AMER, EMEA or APAC)", value:this.defaultValue}, company_type: {consoleString: "company type (e.g. Public, Private, etc.)", value:this.defaultValue}, industry: {consoleString: "industry description", value:this.defaultValue}, @@ -689,6 +696,8 @@ class AddCompany { process.exit() } + + // Defining essential company attributes console.log(chalk.blue.bold('Defining key company attributes like name, type, role and region.')) @@ -696,7 +705,7 @@ class AddCompany { let tmpCompany = await this.wutils.doManual( {name: { consoleString: "name is", - value:this.env.DEFAULT.company, + value:this.env.company, altMessage: "Your company\'s" }}, [], @@ -706,6 +715,7 @@ class AddCompany { // Assign the company's name based upon what was supplied in the env and confirmed by the user myCompany.name = tmpCompany.name + companyPrototype.name.value = myCompany.name // Define the company type const tmpCompanyType = await this.wutils.doList( @@ -763,16 +773,18 @@ class AddCompany { // Quality myCompany.quality = {} // Logo - myCompany.url !== 'Unknown' ? - myCompany.logo_url = await this.getLogo(myCompany.url): - myCompany.logo_url = this.defaultValue + myCompany.logo_url = this.defaultValue + if (myCompany.url !== 'Unknown') { + const logo_url = await this.getLogo(myCompany.url) + logo_url ? myCompany.logo_url = logo_url : myCompany.logo_url = this.defaultValue + } console.log(chalk.green('Finished company definition.')) // Either return the company object or create it if (createObj) { - console.log(chalk.blue.bold(`Saving company ${myCompany.name} to mediumroast.io...`)) + console.log(chalk.blue.bold(`Saving company ${myCompany.name} to mediumroast.io ...`)) this.output.printLine() - return await this.apiController.createObj([myCompany]) + return await this.apiController.createObj2([myCompany]) } else { this.output.printLine() return [true,{status_code: 200, status_msg: `Returning object for ${myCompany.name}`}, myCompany] diff --git a/src/cli/filesystem.js b/src/cli/filesystem.js index b272c1d..b2ef6b3 100644 --- a/src/cli/filesystem.js +++ b/src/cli/filesystem.js @@ -142,7 +142,7 @@ class FilesystemOperators { return [true, 'Directory [' + dirName + '] exists did not create.', null] } } catch (err) { - return [false, 'Did not create directory [' + dirName + '] because: ' + err, null] + return [false, 'Did not create directory [' + dirName + '] because: ' + err.message, err] } } diff --git a/src/cli/installInstructions.js b/src/cli/installInstructions.js index a7ba93a..2b4fc93 100644 --- a/src/cli/installInstructions.js +++ b/src/cli/installInstructions.js @@ -5,7 +5,9 @@ work at all. Please exit this tool until you can confirm the application is ins pressing \'Control-C\'. If you attempt to continue without the application installed this tool will fail. -Details for installation of the GitHub application are located at: https://www.mediumroast.io/app +You can learn more about and install the GitHub Application by visiting the following URL: https://github.com/apps/mediumroast-for-github + +A more detailed product description, help and other related documentation ca be found here: https://www.mediumroast.io/ ` export default installText From 5526baada693e7b48c9a038f3a61bfa8fd8c5f93 Mon Sep 17 00:00:00 2001 From: mihay42 Date: Sat, 13 Jan 2024 18:12:18 -0800 Subject: [PATCH 2/2] Ready for test --- cli/mrcli-company.js | 10 +-- cli/mrcli-interaction.js | 12 ++-- cli/mrcli-setup.js | 4 +- cli/mrcli.js | 2 +- package.json | 2 +- src/api/gitHubServer.js | 27 ++++++-- src/api/github.js | 110 +++++++++++++++---------------- src/cli/companyWizard.js | 26 ++++++-- src/cli/interactionWizard.js | 121 ++++++++++++++++++++++------------- 9 files changed, 188 insertions(+), 126 deletions(-) diff --git a/cli/mrcli-company.js b/cli/mrcli-company.js index 3143a63..5a95fcb 100755 --- a/cli/mrcli-company.js +++ b/cli/mrcli-company.js @@ -214,7 +214,7 @@ if (myArgs.report) { stat = foundObjects[1] results = foundObjects[2] } else if (myArgs.update) { - const lockResp = companyCtl.checkForLock() + const lockResp = await companyCtl.checkForLock() if(lockResp[0]) { console.log(`ERROR: ${lockResp[1].status_msg}`) process.exit(-1) @@ -233,7 +233,7 @@ if (myArgs.report) { } // TODO: Need to reimplement the below to account for GitHub } else if (myArgs.delete) { - const lockResp = companyCtl.checkForLock() + const lockResp = await companyCtl.checkForLock() if(lockResp[0]) { console.log(`ERROR: ${lockResp[1].status_msg}`) process.exit(-1) @@ -261,7 +261,7 @@ if (myArgs.report) { process.exit(-1) } } else if (myArgs.add_wizard) { - const lockResp = companyCtl.checkForLock() + const lockResp = await companyCtl.checkForLock() if(lockResp[0]) { console.log(`ERROR: ${lockResp[1].status_msg}`) process.exit(-1) @@ -270,10 +270,10 @@ if (myArgs.report) { const newCompany = new AddCompany(myEnv, {github: gitHubCtl, interaction: interactionCtl, company: companyCtl, user: userCtl}) const result = await newCompany.wizard() if(result[0]) { - console.log('SUCCESS: Created new company in the backend') + console.log(`SUCCESS: ${result[1].status_msg}`) process.exit(0) } else { - console.log(`ERROR: Failed to create company object with:, ${result[2]}`) + console.log(`ERROR: ${result[1].status_msg}`) process.exit(-1) } } else if (myArgs.reset_by_type) { diff --git a/cli/mrcli-interaction.js b/cli/mrcli-interaction.js index 778faee..8215783 100755 --- a/cli/mrcli-interaction.js +++ b/cli/mrcli-interaction.js @@ -184,7 +184,7 @@ if (myArgs.report) { stat = foundObjects[1] results = foundObjects[2] } else if (myArgs.update) { - const lockResp = interactionCtl.checkForLock() + const lockResp = await interactionCtl.checkForLock() if(lockResp[0]) { console.log(`ERROR: ${lockResp[1].status_msg}`) process.exit(-1) @@ -202,7 +202,7 @@ if (myArgs.report) { process.exit(-1) } } else if (myArgs.delete) { - const lockResp = interactionCtl.checkForLock() + const lockResp = await interactionCtl.checkForLock() if(lockResp[0]) { console.log(`ERROR: ${lockResp[1].status_msg}`) process.exit(-1) @@ -226,7 +226,7 @@ if (myArgs.report) { process.exit(-1) } } else if (myArgs.add_wizard) { - const lockResp = interactionCtl.checkForLock() + const lockResp = await interactionCtl.checkForLock() if(lockResp[0]) { console.log(`ERROR: ${lockResp[1].status_msg}`) process.exit(-1) @@ -234,14 +234,14 @@ if (myArgs.report) { const newInteraction = new AddInteraction(myEnv, {github: gitHubCtl, interaction: interactionCtl, company: companyCtl, user: userCtl}) const result = await newInteraction.wizard() if(result[0]) { - console.log(`SUCCESS: Added new interaction object(s).`) + console.log(`SUCCESS: ${result[1].status_msg}`) process.exit(0) } else { - console.log(`ERROR: Unable to add interaction object due to [${result[1].status_msg}].`) + console.log(`ERROR: ${result[1].status_msg}.`) process.exit(-1) } } else if (myArgs.reset_by_type) { - console.error('ERROR (%d): Reset by type not implemented.', -1) + console.log('ERROR: Reset by type not implemented.') process.exit(-1) const lockResp = interactionCtl.checkForLock() if(lockResp[0]) { diff --git a/cli/mrcli-setup.js b/cli/mrcli-setup.js index 3445431..878a818 100755 --- a/cli/mrcli-setup.js +++ b/cli/mrcli-setup.js @@ -420,11 +420,11 @@ let spinner // Save the companies to GitHub spinner = ora(chalk.bold.blue('Saving companies to GitHub ... ')) spinner.start() // Start the spinner - const companyResp = await companyCtl.createObj2([owningCompany, firstCompany]) + const companyResp = await companyCtl.createObj([owningCompany, firstCompany]) spinner.stop() // Stop the spinner // If the company creation failed then exit if(!companyResp[0]) { - console.log(chalk.red.bold(`FAILED: ${companyResp[1].status_msg}, you may need to clean up the repo.`)) + console.log(chalk.red.bold(`FAILED: ${companyResp[1].status_msg}, you may need to clean up the repository.`)) process.exit(-1) } cliOutput.printLine() diff --git a/cli/mrcli.js b/cli/mrcli.js index 3d18cb8..ea67ad9 100755 --- a/cli/mrcli.js +++ b/cli/mrcli.js @@ -14,7 +14,7 @@ import program from 'commander' program .name('mrcli') - .version('0.4.34') + .version('0.4.35') .description('mediumroast.io command line interface') .command('setup', 'setup the mediumroast.io system via the command line').alias('f') .command('interaction', 'manage and report on mediumroast.io interaction objects').alias('i') diff --git a/package.json b/package.json index dd406e8..5379c45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mediumroast_js", - "version": "0.4.34", + "version": "0.4.35", "description": "A Command Line Interface (CLI) and Javascript SDK to interact with mediumroast.io.", "main": "cli/mrcli.js", "scripts": { diff --git a/src/api/gitHubServer.js b/src/api/gitHubServer.js index 58cd81b..6dbb4ec 100644 --- a/src/api/gitHubServer.js +++ b/src/api/gitHubServer.js @@ -90,14 +90,24 @@ class baseObjects { const allObjectsResp = await this.serverCtl.readObjects(this.objType) allObjects = allObjectsResp[2].mrJson } + // If the length of allObjects is 0 then return an error + // This will occur when there are no objects of the type in the backend + if(allObjects.length === 0) { + return [false, {status_code: 404, status_msg: `no ${this.objType} found`}, null] + } for(const obj in allObjects) { let currentObject - attribute == 'name' ? currentObject = allObjects[obj][attribute].toLowerCase() : null + attribute == 'name' ? currentObject = allObjects[obj][attribute].toLowerCase() : currentObject = allObjects[obj][attribute] if(currentObject === value) { myObjects.push(allObjects[obj]) } } - return [true, `SUCCESS: found all objects where ${attribute} = ${value}`, myObjects] + + if (myObjects.length === 0) { + return [false, {status_code: 404, status_msg: `no ${this.objType} found where ${attribute} = ${value}`}, null] + } else { + return [true, `SUCCESS: found all objects where ${attribute} = ${value}`, myObjects] + } } /** @@ -107,11 +117,11 @@ class baseObjects { * @param {Array} objs - the objects to create in the backend * @returns {Array} the results from the called function mrRest class */ - async createObj(objs) { - return await this.serverCtl.createObjects(this.objType, objs) - } + // async createObj1(objs) { + // return await this.serverCtl.createObjects(this.objType, objs) + // } - async createObj2(objs) { + async createObj(objs) { // Create the repoMetadata object let repoMetadata = { containers: { @@ -361,6 +371,7 @@ class Companies extends baseObjects { // Return the response return [true, {status_code: 200, status_msg: `deleted company [${objName}] and all linked interactions`}, null] } + } @@ -397,6 +408,10 @@ class Interactions extends baseObjects { } return await super.deleteObj(objName, source) } + + async findByHash(hash) { + return this.findByX('file_hash', hash) + } } // Export classes for consumers diff --git a/src/api/github.js b/src/api/github.js index 4ce2a0c..18ff474 100644 --- a/src/api/github.js +++ b/src/api/github.js @@ -855,63 +855,63 @@ class GitHubFunctions { } - /** - * @function createObjects - * @description Creates objects in a specified container using the GitHub API. - * @async - * @param {string} containerName - The name of the container to create the objects in. - * @param {object} objs - The objects to create in the container. - * @returns {Promise} A promise that resolves to the decoded contents of the object. - * @throws {Error} If an error occurs while getting the content or parsing it. - * @memberof GitHubFunctions - */ - async createObjects(containerName, objs) { - // Call the method above to check for a lock - const lockExists = await this.checkForLock(containerName) - // If the lock exists return an error - if(lockExists[0]) { return [false, `ERROR: The container [${containerName}] is locked unable to create objects.`] } - - - // Lock the container - const locked = await this.lockContainer(containerName) - // Check to see if the container was locked and return the error if not - if(!locked[0]) { return [false, `ERROR: Unable to lock [${containerName}] cannot create new objects.`, locked] } - const lockSha = locked[2].data.content.sha + // /** + // * @function createObjects + // * @description Creates objects in a specified container using the GitHub API. + // * @async + // * @param {string} containerName - The name of the container to create the objects in. + // * @param {object} objs - The objects to create in the container. + // * @returns {Promise} A promise that resolves to the decoded contents of the object. + // * @throws {Error} If an error occurs while getting the content or parsing it. + // * @memberof GitHubFunctions + // */ + // async createObjects(containerName, objs) { + // // Call the method above to check for a lock + // const lockExists = await this.checkForLock(containerName) + // // If the lock exists return an error + // if(lockExists[0]) { return [false, `ERROR: The container [${containerName}] is locked unable to create objects.`] } + + + // // Lock the container + // const locked = await this.lockContainer(containerName) + // // Check to see if the container was locked and return the error if not + // if(!locked[0]) { return [false, `ERROR: Unable to lock [${containerName}] cannot create new objects.`, locked] } + // const lockSha = locked[2].data.content.sha - // Call the method above createBranchFromMain to create a new branch - const branchCreated = await this.createBranchFromMain() - // Check to see if the branch was created - if(!branchCreated[0]) { return [false, `ERROR: Unable to create new branch`, branchCreated] } - const branchSha = branchCreated[2].data.object.sha - const branchName = branchCreated[2].data.ref - - // Call the method above to read the objects - const readResponse = await this.readObjects(containerName) - // Check to see if the read was successful - if(!readResponse[0]) { return [false, `ERROR: unable to read the source objects [${containerName}/${this.objectFiles[containerName]}].`, readResponse] } - const objectSha = readResponse[2].data.sha - - // Merge the objects with the updated object which are an array of objects - let mergedObjects = [...readResponse[2].mrJson, ...objs] - - - // Write the objects to the new branch - const writeResponse = await this.writeObject(containerName, mergedObjects, branchName, objectSha) - // Check to see if the write was successful and return the error if not - if(!writeResponse[0]) { return [false,`ERROR: Unable to write the objects to [${containerName}/${this.objectFiles[containerName]}].`, writeResponse] } - - // Merge the branch to main - const mergeResponse = await this.mergeBranchToMain(branchName, branchSha) - // Check to see if the merge was successful and return the error if not - if(!mergeResponse[0]) { return [false,`ERROR: Unable to merge the branch to main.`, mergeResponse] } - - // Unlock the container - const unlocked = await this.unlockContainer(containerName, lockSha) - if(!unlocked[0]) { return [false, `ERROR: Unable to unlock the container, objects may have been written please check [${containerName}] for objects and the lock file.`, unlocked] } + // // Call the method above createBranchFromMain to create a new branch + // const branchCreated = await this.createBranchFromMain() + // // Check to see if the branch was created + // if(!branchCreated[0]) { return [false, `ERROR: Unable to create new branch`, branchCreated] } + // const branchSha = branchCreated[2].data.object.sha + // const branchName = branchCreated[2].data.ref + + // // Call the method above to read the objects + // const readResponse = await this.readObjects(containerName) + // // Check to see if the read was successful + // if(!readResponse[0]) { return [false, `ERROR: unable to read the source objects [${containerName}/${this.objectFiles[containerName]}].`, readResponse] } + // const objectSha = readResponse[2].data.sha + + // // Merge the objects with the updated object which are an array of objects + // let mergedObjects = [...readResponse[2].mrJson, ...objs] + + + // // Write the objects to the new branch + // const writeResponse = await this.writeObject(containerName, mergedObjects, branchName, objectSha) + // // Check to see if the write was successful and return the error if not + // if(!writeResponse[0]) { return [false,`ERROR: Unable to write the objects to [${containerName}/${this.objectFiles[containerName]}].`, writeResponse] } + + // // Merge the branch to main + // const mergeResponse = await this.mergeBranchToMain(branchName, branchSha) + // // Check to see if the merge was successful and return the error if not + // if(!mergeResponse[0]) { return [false,`ERROR: Unable to merge the branch to main.`, mergeResponse] } + + // // Unlock the container + // const unlocked = await this.unlockContainer(containerName, lockSha) + // if(!unlocked[0]) { return [false, `ERROR: Unable to unlock the container, objects may have been written please check [${containerName}] for objects and the lock file.`, unlocked] } - // Return success with number of objects written - return [true, `SUCCESS: [${objs.length}] object(s) written to [${containerName}/${this.objectFiles[containerName]}].`, null] - } + // // Return success with number of objects written + // return [true, `SUCCESS: [${objs.length}] object(s) written to [${containerName}/${this.objectFiles[containerName]}].`, null] + // } } export default GitHubFunctions \ No newline at end of file diff --git a/src/cli/companyWizard.js b/src/cli/companyWizard.js index 37f82fd..76d5138 100755 --- a/src/cli/companyWizard.js +++ b/src/cli/companyWizard.js @@ -14,7 +14,6 @@ import ora from "ora" import mrRest from "../api/scaffold.js" import WizardUtils from "./commonWizard.js" import CLIOutput from "./output.js" -import crypto from "node:crypto" import axios from "axios" @@ -652,6 +651,8 @@ class AddCompany { creator: {consoleString: "", value: myUser.login}, // Set the creator to the GitHub user creator_id: {consoleString: "", value: myUser.id}, // Set the creator to the GitHub user creator_name: {consoleString: "", value: myUser.name}, // Set the creator to the GitHub user + creation_date: {consoleString: "", value: new Date().toISOString()}, // Set the creation date to now + modification_date: {consoleString: "", value: new Date().toISOString()}, // Set the modification date to now region: {consoleString: "region (AMER, EMEA or APAC)", value:this.defaultValue}, company_type: {consoleString: "company type (e.g. Public, Private, etc.)", value:this.defaultValue}, industry: {consoleString: "industry description", value:this.defaultValue}, @@ -717,6 +718,14 @@ class AddCompany { myCompany.name = tmpCompany.name companyPrototype.name.value = myCompany.name + // Check to see if the company name is already in the system using findByName + const companyExists = await this.apiController.findByName(myCompany.name) + if (companyExists[0]) { + // Return the companyExists object with an error message + return [false,{status_code: 400, status_msg: `company [${myCompany.name}] already exists, duplicates not allowed, exiting.`}, companyExists[2]] + } + + // Define the company type const tmpCompanyType = await this.wutils.doList( "What type of company is this?", @@ -782,9 +791,18 @@ class AddCompany { // Either return the company object or create it if (createObj) { - console.log(chalk.blue.bold(`Saving company ${myCompany.name} to mediumroast.io ...`)) - this.output.printLine() - return await this.apiController.createObj2([myCompany]) + const spinner = ora(chalk.bold.blue(`Saving ${myCompany.name} to GitHub ... `)) + spinner.start() // Start the spinner + const myCompanyResp = await this.apiController.createObj([myCompany]) + spinner.stop() // Stop the spinner + // Check to see if the company was created + if (myCompanyResp[0]) { + this.output.printLine() + return [true,{status_code: 200, status_msg: `created [${myCompany.name}]`}, myCompanyResp[2]] + } else { + this.output.printLine() + return [false,{status_code: 400, status_msg: `unable to create [${myCompany.name}]`}, myCompanyResp[2]] + } } else { this.output.printLine() return [true,{status_code: 200, status_msg: `Returning object for ${myCompany.name}`}, myCompany] diff --git a/src/cli/interactionWizard.js b/src/cli/interactionWizard.js index e964df8..5b7aac0 100755 --- a/src/cli/interactionWizard.js +++ b/src/cli/interactionWizard.js @@ -17,6 +17,7 @@ import CLIOutput from "./output.js" import FilesystemOperators from "./filesystem.js" import * as progress from 'cli-progress' import ora from 'ora' +import crypto from 'crypto' class AddInteraction { /** @@ -99,18 +100,25 @@ class AddInteraction { async getValidPath() { const pathPrototype = { - path: {consoleString: 'full path to the file or directory (e.g., /dir/subdir or /dir/file.ext)', value:this.defaultValue} + path: {consoleString: 'full path to the directory (e.g., /dir/subdir)', value:this.defaultValue} } let myPath = await this.wutils.doManual(pathPrototype) const [success, message, result] = this.fileSystem.checkFilesystemObject(myPath.path) - if(!success) { - console.log(chalk.red.bold('\t-> The file system object wasn\'t detected, perhaps the path/file name isn\'t correct? Trying again...')) + const myObjectType = this.fileSystem.checkFilesystemObjectType(myPath.path) + if(!success || myObjectType[2].isFile()) { + console.log(chalk.red.bold(`The directory path wasn\'t resolved correctly. Here\'s your input [${myPath.path}]. Let\'s try again.`)) myPath = await this.getValidPath() } - console.log(myPath.path) return myPath.path } + // Create a function that computes the hash of base64 encoded data and returns the hash + computeHash(fileData) { + const hash = crypto.createHash('sha256') + hash.update(fileData) + return hash.digest('hex') + } + async ingestInteractions(branchName, branchSha) { // Pre-define the final object let myFiles = [] @@ -118,50 +126,52 @@ class AddInteraction { // Get a valid path const myPath = await this.getValidPath() - // Check to see if this is a file or a directory using the file system object - const myObjectType = this.fileSystem.checkFilesystemObjectType(myPath) - if(myObjectType[2].isFile()) { - // Set up the progress bar - this.progressBar.start(1, 0) + // List all files in the directory and process them one at a time + const allFiles = this.fileSystem.listAllFiles(myPath) + // Start the progress bar + const totalInteractions = allFiles[2].length - 1 + this.progressBar.start(totalInteractions, 0) + // Iterate through each file in the directory + for(const myIdx in allFiles[2]) { + // Set the file name for easier readability + let fileName = allFiles[2][myIdx] + // Skip files that start with . including present and parent working directories + if(fileName.indexOf('.') === 0) { + // Increment the progress bar + this.progressBar.increment() + continue + } // Read the blob and return contents base64 encoded - const fileData = fileSystem.readBlobFile(myPath) - // Upload the file to GitHub - const myContents = await this.uploadFile(myPath, fileData[2], branchName, branchSha) - // Save the results - myFiles.push(myContents[2]) - // Increment the progress bar - this.progressBar.increment() - } else { - // List all files in the directory and process them one at a time - const allFiles = this.fileSystem.listAllFiles(myPath) - // Start the progress bar - this.progressBar.start(allFiles[2].length, 0) - // Iterate through each file in the directory - for(const myIdx in allFiles[2]) { - // Set the file name for easier readability - let fileName = allFiles[2][myIdx] - // Skip files that start with . including present and parent working directories - if(fileName.indexOf('.') === 0) { continue } - // Read the blob and return contents base64 encoded - const fileData = this.fileSystem.readBlobFile(`${myPath}/${fileName}`) - // Upload the file to GitHub - const myContents = await this.uploadFile(`${myPath}/${fileName}`, fileData[2], branchName, branchSha) - // Remove the extesion from the file name and save the file name to the myFiles array - const fullFileName = fileName - const fileBits = fileName.split('.') - const shortFilename = fileBits[fileBits.length - 1] - fileName = fileName.replace(`.${shortFilename}`, '') - // We need to same the object name and the actual file name for later retrieval - myFiles.push({interactionName: fileName, fileName: fullFileName}) + const fileData = this.fileSystem.readBlobFile(`${myPath}/${fileName}`) + // Compute the hash of the file + const fileHash = this.computeHash(fileData[2]) + // Check to see if the file is already in the backend by checking the hash + const fileExists = await this.interactionCtl.findByHash(fileHash) + // If the file exists, skip it + if(fileExists[0]) { // Increment the progress bar this.progressBar.increment() + // Add the file to the myFiles array, but as a special case that says it already exists + myFiles.push({interactionName: fileName, fileName: fileName, fileExists: true, fileHash: fileHash}) + continue } - // Stop the progress bar - this.progressBar.stop() - - // Return the result of uploaded files - return myFiles + // Upload the file to GitHub + const myContents = await this.uploadFile(`${myPath}/${fileName}`, fileData[2], branchName, branchSha) + // Remove the extesion from the file name and save the file name to the myFiles array + const fullFileName = fileName + const fileBits = fileName.split('.') + const shortFilename = fileBits[fileBits.length - 1] + fileName = fileName.replace(`.${shortFilename}`, '') + // We need to same the object name and the actual file name for later retrieval + myFiles.push({interactionName: fileName, fileName: fullFileName, fileExists: false, fileHash: fileHash}) + // Increment the progress bar + this.progressBar.increment() } + // Stop the progress bar + this.progressBar.stop() + + // Return the result of uploaded files + return myFiles } async getInteractionType () { @@ -209,15 +219,26 @@ class AddInteraction { async createInteractionObject(interactionPrototype, myFiles, myCompany) { this.output.printLine() + // Create a duplicate count + let duplicateCount = 0 // Loop through each file and create an interaction object let myInteractions = [] for(const myFile in myFiles) { + // If the file already exists, skip it + if(myFiles[myFile].fileExists) { + console.log(chalk.red.bold(`Skipping file [${myFiles[myFile].interactionName}] because it already exists.`)) + // Increment the duplicate count + duplicateCount++ + continue + } // Assign each value from the prototype to the interaction object let myInteraction = {} // Loop through each attribute in the prototype and assign the value to the interaction object for(const attribute in interactionPrototype) { myInteraction[attribute] = interactionPrototype[attribute].value } + // Set the file hash + myInteraction.file_hash = myFiles[myFile].fileHash // Set the name of the interaction to the file name myInteraction.name = myFiles[myFile].interactionName console.log(chalk.blue.bold(`Setting details for [${myInteraction.name}]`)) @@ -240,7 +261,7 @@ class AddInteraction { } this.output.printLine() } - return [myInteractions, myCompany.linked_interactions] + return [myInteractions, myCompany.linked_interactions, duplicateCount] } /** @@ -321,6 +342,7 @@ class AddInteraction { groups: {consoleString: "", value: `${this.env.gitHubOrg}:${myUser.login}`}, // Set to the organization and user, reserved for future use creation_date: {consoleString: "", value: myDateString}, // Set to the current date modification_date: {consoleString: "", value: myDateString}, // Set to the current date + file_hash: {consoleString: "", value: this.defaultValue}, // Assigned to detect duplicates } // Catch the container for updates @@ -346,7 +368,7 @@ class AddInteraction { const files = await this.ingestInteractions(caught[2].branch.name, caught[2].branch.sha) // Create the interaction object - let [myInteractions, linkedInteractions] = await this.createInteractionObject( + let [myInteractions, linkedInteractions, duplicateCount] = await this.createInteractionObject( interactionPrototype, files, myCompany @@ -368,8 +390,13 @@ class AddInteraction { // Create the new interactions mySpinner = new ora('Writing interaction objects ...') mySpinner.start() + // Capture the number of interactions + const interactionCount = myInteractions.length + // Append the new interactions to the existing interactions myInteractions = [...myInteractions, ...caught[2].containers.Interactions.objects] + + // Write the new interactions to the backend const createdInteractions = await this.githubCtl.writeObject( this.objectType, @@ -403,7 +430,9 @@ class AddInteraction { mySpinner.start() const released = await this.githubCtl.releaseContainer(caught[2]) mySpinner.stop() - return released + // Return the result of the write including the interaction count and duplicate count + return [true, {status_code: 200, status_msg: `created ${interactionCount} interactions with ${duplicateCount} duplicates detected`}, createdInteractions[2]] + } }