Source: submission/index.js

/**
 * @module submission
 */
const submissionRepo = require("../../repositories/submission");
const digest = require("../digest");
const linkedinAPI = require("../linkedin_learning");
const log = require("../../util/log");
const courseService = require("../course");
const videoService = require("../video");
const mail = require("../mail");
const util = require("../../util/");
const capabilityService = require("../capability");
const categoryService = require("../category");
const competencyService = require("../competency");

/**
 * Fetch submission from database by id.
 * @param {String} id
 * @return {Object} submission object
 */
const fetchById = async (id) => {
    const findResult = await submissionRepo.findById(id);
    if (findResult.rows.length === 0) {
        throw new Error("No submission found with ID - " + id);
    }
    return findResult.rows[0];
};

/**
 * Fetch all submissions from database.
 * @return {Array} submissions
 */
const fetchAll = async () => {
    const findResult = await submissionRepo.findAll();
    return findResult.rows;
};
/**
 * Generate unique submission id by hashing
 * the email, timestamp and server secret.
 * @param {String} email
 * @param {String} timestamp
 * @return {String} unique submission id
 */
const generateSubmissionID = (email, timestamp) => {
    return util.base64ToURLSafe(
        digest.hashVarargInBase64(
            email,
            timestamp,
            process.env.SERVER_SECRET,
        ),
    );
};

/**
 * Update the status of a submission
 * to rejected.
 * @param {String} id
 */
const rejectSubmission = async (id) => {
    const submission = await fetchById(id);
    submission.status = 'rejected';
    await updateSubmission(submission);
};

/**
 * Map a submission to given RDF parameters and
 * update record in database.
 * @param {String} id
 * @param {Number} capability id of capability
 * @param {Number} category id of category
 * @param {Number} competency id of competency
 * @param {any} phases array or single id number
 */
const mapSubmission = async (id, capability, category, competency, phases) => {
    const submission = await fetchById(id);
    submission.data.capability = capability;
    submission.data.category = category;
    submission.data.competency = competency;
    submission.data.phases = phases;
    const capabilityObj = await capabilityService.fetchById(capability);
    const categoryObj = await categoryService.fetchById(category);
    const competencyObj = await competencyService.fetchById(competency);
    submission.data.capabilityTitle = capabilityObj.title;
    submission.data.categoryTitle = categoryObj.title;
    submission.data.competencyTitle = competencyObj.title;
    await updateSubmission(submission);
};

/**
 * Publish a submission: insert it as a new learning object and
 * update its status to published.
 * @param {String} id
 */
const publishSubmission = async (id) => {
    const submission = await fetchById(id);
    if (!submission.data.urn) {
        throw new Error("Mustn't publish submission without a URN!");
    }
    if (
        !submission.data.capability ||
        !submission.data.category ||
        !submission.data.competency ||
        !submission.data.phases ||
        !submission.data.capabilityTitle ||
        !submission.data.categoryTitle ||
        !submission.data.competencyTitle
    ) {
        throw new Error("Mustn't publish submission without an RDF mapping!");
    }
    submission.data.type === "COURSE" ?
        await courseService.addNewCourse(submission.data) :
        await videoService.addNewVideo(submission.data);
    submission.status = "published";
    await updateSubmission(submission);
};

/**
 * Insert new submission in the database and send
 * tracking link to user if an email was provided.
 * @param {String} type course / video
 * @param {String} title
 * @param {String} hyperlink
 * @param {String} email
 */
const insertNewSubmission = async (type, title, hyperlink, email) => {
    const timestamp = (new Date()).toUTCString();
    const id = generateSubmissionID(email, timestamp);
    const insertionResult = await submissionRepo.insert({
        id,
        status: "processing",
        submitter: email ? email : "anonymous",
        data: {timestamp, type, title, hyperlink},
    });
    if (email) {
        mail.sendEmail(
            process.env.SUBMISSION_EMAIL_ADDRESS,
            email,
            "Your RDFmapped Content Submission",
            `Thank you very much for submitting content to the RDFmapped website, we really appreciate the contribution.\n` +
            `You can track the progress of you submission via the following link: https://rdfmapped.com/submission/${id}`,
            `<p>Thank you very much for submitting content to the RDFmapped website, we really appreciate the contribution.</p>` +
            `You can track the progress of you submission via the following link: ` +
            `<a href="https://rdfmapped.com/submission/${id}" target="_blank" rel="noopener noreferrer">` +
            `https://rdfmapped.com/submission/${id}</a>`);
    }
    await attemptToFindURN(insertionResult.rows[0]);
};

/**
 * Verify the integrity of a submission via the LinkedIn
 * Learning API and update its status accordingly.
 * Upon success, the submission's Unique Resource Identifier
 * is set to match the LinkedIn Learning URN.
 * @param {Object} submission
 */
const attemptToFindURN = async (submission) => {
    try {
        log.info("Attempting to find URN for submission(%s)", submission.id);
        const result = await linkedinAPI.fetchURNByContent(submission, submission.data.type);
        if (result) {
            log.info("Found URN(%s) for submission(%s)", result, submission.id);
            submission.data.urn = result;
        }
        submission.status = result ? "pending" : "failed";
        await updateSubmission(submission);
    } catch (error) {
        /* istanbul ignore next */
        log.error("Finding URN for submission(%s) failed, err: " +
            error.message, submission.id);
    }
};

/**
 * Update a submission object in the database.
 * @param {Object} submission
 */
const updateSubmission = async (submission) => {
    await submissionRepo.update(submission);
};

module.exports = {
    fetchById,
    insertNewSubmission,
    fetchAll,
    rejectSubmission,
    publishSubmission,
    mapSubmission,
};