Source: cache/index.js

/**
 * @module cache
 */
const NodeCache = require("node-cache");
const log = require("../../util/log");
const linkedinApi = require("../linkedin_learning");
const fs = require('fs');

/**
 * The main server cache.
 */
const modulesCache = new NodeCache({
    checkperiod: 0,
});

/**
 * Get a cached object.
 * @param {String} key
 * @return {Object} cached object
 */
const get = (key) => {
    return modulesCache.get(key);
};

/**
 * Cache an object.
 * @param {String} key
 * @param {String} value
 */
const set = (key, value) => {
    modulesCache.set(key, value);
};

/**
 * Return true if key exists in cache.
 * @param {String} key
 * @return {Boolean} true if exists
 */
const has = (key) => {
    return modulesCache.has(key);
};

/**
 * Remove an object from the cache.
 * @param {String} key
 */
const del = (key) => {
    modulesCache.del(key);
};

/**
 * Flush the cache.
 */
const flush = () => {
    log.info("About to flush CACHE, current stats: %s", modulesCache.getStats());
    modulesCache.flushAll();
    log.info("Flushed CACHE, stats reset: %s", modulesCache.getStats());
};

/**
 * Log the cache stats.
 */
const logStats = () => {
    log.info("CACHE stats: %s", modulesCache.getStats());
};

/**
 * Update all cached learning objects from LinkedIn Learning API.
 * Log the results and write out failed objects to a separate log file.
 */
const updateAllFromAPI = async () => {
    log.info("Attempting to update CACHE from LinkedIn-L API..");

    const courses = [];
    const videos = [];
    let successCount = 0;
    let errorCount = 0;
    const objectsWithoutURN = {
        courses: [],
        videos: [],
    };
    for await (const key of modulesCache.keys()) {
        // check if object has an LinkedIn Learning API ID
        const val = modulesCache.get(key);
        if (/^urn:li:/.test(val.urn)) {
            const learningObj = await linkedinApi.fetchLearningObject(val.urn);
            if (learningObj) {
                ++successCount;
                // update object fields
                val.title = learningObj.title.value;
                val.hyperlink = learningObj.details.urls.webLaunch;
                val.longDescription = learningObj.details.descriptionIncludingHtml ?
                    learningObj.details.descriptionIncludingHtml.value : null;
                val.shortDescription = learningObj.details.shortDescriptionIncludingHtml ?
                    learningObj.details.shortDescriptionIncludingHtml.value : null;
                val.picture = learningObj.details.images.primary ?
                    learningObj.details.images.primary : null;
                val.length = learningObj.details.timeToComplete ?
                    learningObj.details.timeToComplete.duration : null;
                // store updated object
                modulesCache.set(key, val);
            } else {
                ++errorCount;
            }
            // add object to collections despite update
            if (/^course-/.test(key)) {
                courses.push(modulesCache.get(key));
            } else if (/^video-/.test(key)) {
                videos.push(modulesCache.get(key));
            }
        } else {
            // log discarded object for not having a URN
            if (/^course-/.test(key)) {
                objectsWithoutURN.courses.push(val);
            } else if (/^video-/.test(key)) {
                objectsWithoutURN.videos.push(val);
            }
            modulesCache.del(key);
        }
    }
    // update collections
    modulesCache.set("courses", courses);
    modulesCache.set("videos", videos);
    errorCount === 0 ?
        log.info("CACHE update from LinkedIn-L API finished without errors: %s objects fetched successfully", successCount) :
        log.warn("CACHE update from LinkedIn-L API had at least one error: %s succeeded, %s failed", successCount, errorCount);
    logStats();

    fs.writeFileSync(`./log/objects_without_urn_${(new Date()).toUTCString()}`, JSON.stringify(objectsWithoutURN));
};

/**
 * Update a single cached object from LinkedIn Learning API.
 * @param {String} key
 */
const updateFromAPI = async (key) => {
    log.info("Attempting to update CACHE(%s) from LinkedIn-L API..", key);
    try {
        const val = modulesCache.get(key);
        const learningObj = await linkedinApi.fetchLearningObject(val.urn);
        if (learningObj) {
            // update object fields
            val.title = learningObj.title.value;
            val.hyperlink = learningObj.details.urls.webLaunch;
            val.longDescription = learningObj.details.descriptionIncludingHtml ?
                learningObj.details.descriptionIncludingHtml.value : null;
            val.shortDescription = learningObj.details.shortDescriptionIncludingHtml ?
                learningObj.details.shortDescriptionIncludingHtml.value : null;
            val.picture = learningObj.details.images.primary ?
                learningObj.details.images.primary : null;
            val.length = learningObj.details.timeToComplete ?
                learningObj.details.timeToComplete.duration : null;
            // store updated object
            modulesCache.set(key, val);
            // add object to collections if updated
            if (/^course-/.test(key)) {
                const courses = modulesCache.get("courses");
                courses.push(modulesCache.get(key));
                modulesCache.set("courses", courses);
            } else if (/^video-/.test(key)) {
                const videos = modulesCache.get("videos");
                videos.push(modulesCache.get(key));
                modulesCache.set("videos", videos);
            }
        }
        log.info("Successfully updated CACHE(%s) from LinkedIn-L API..", key);
    } catch (error) {
        log.error("Failed to update CACHE(%s from LinkedIn-L API.., err: " + error.message, key);
    }
};

module.exports = {
    get,
    set,
    has,
    del,
    flush,
    logStats,
    updateAllFromAPI,
    updateFromAPI,
};