Source: submission/index.js

  1. /**
  2. * @module submission
  3. */
  4. const submissionRepo = require("../../repositories/submission");
  5. const digest = require("../digest");
  6. const linkedinAPI = require("../linkedin_learning");
  7. const log = require("../../util/log");
  8. const courseService = require("../course");
  9. const videoService = require("../video");
  10. const mail = require("../mail");
  11. const util = require("../../util/");
  12. const capabilityService = require("../capability");
  13. const categoryService = require("../category");
  14. const competencyService = require("../competency");
  15. /**
  16. * Fetch submission from database by id.
  17. * @param {String} id
  18. * @return {Object} submission object
  19. */
  20. const fetchById = async (id) => {
  21. const findResult = await submissionRepo.findById(id);
  22. if (findResult.rows.length === 0) {
  23. throw new Error("No submission found with ID - " + id);
  24. }
  25. return findResult.rows[0];
  26. };
  27. /**
  28. * Fetch all submissions from database.
  29. * @return {Array} submissions
  30. */
  31. const fetchAll = async () => {
  32. const findResult = await submissionRepo.findAll();
  33. return findResult.rows;
  34. };
  35. /**
  36. * Generate unique submission id by hashing
  37. * the email, timestamp and server secret.
  38. * @param {String} email
  39. * @param {String} timestamp
  40. * @return {String} unique submission id
  41. */
  42. const generateSubmissionID = (email, timestamp) => {
  43. return util.base64ToURLSafe(
  44. digest.hashVarargInBase64(
  45. email,
  46. timestamp,
  47. process.env.SERVER_SECRET,
  48. ),
  49. );
  50. };
  51. /**
  52. * Update the status of a submission
  53. * to rejected.
  54. * @param {String} id
  55. */
  56. const rejectSubmission = async (id) => {
  57. const submission = await fetchById(id);
  58. submission.status = 'rejected';
  59. await updateSubmission(submission);
  60. };
  61. /**
  62. * Map a submission to given RDF parameters and
  63. * update record in database.
  64. * @param {String} id
  65. * @param {Number} capability id of capability
  66. * @param {Number} category id of category
  67. * @param {Number} competency id of competency
  68. * @param {any} phases array or single id number
  69. */
  70. const mapSubmission = async (id, capability, category, competency, phases) => {
  71. const submission = await fetchById(id);
  72. submission.data.capability = capability;
  73. submission.data.category = category;
  74. submission.data.competency = competency;
  75. submission.data.phases = phases;
  76. const capabilityObj = await capabilityService.fetchById(capability);
  77. const categoryObj = await categoryService.fetchById(category);
  78. const competencyObj = await competencyService.fetchById(competency);
  79. submission.data.capabilityTitle = capabilityObj.title;
  80. submission.data.categoryTitle = categoryObj.title;
  81. submission.data.competencyTitle = competencyObj.title;
  82. await updateSubmission(submission);
  83. };
  84. /**
  85. * Publish a submission: insert it as a new learning object and
  86. * update its status to published.
  87. * @param {String} id
  88. */
  89. const publishSubmission = async (id) => {
  90. const submission = await fetchById(id);
  91. if (!submission.data.urn) {
  92. throw new Error("Mustn't publish submission without a URN!");
  93. }
  94. if (
  95. !submission.data.capability ||
  96. !submission.data.category ||
  97. !submission.data.competency ||
  98. !submission.data.phases ||
  99. !submission.data.capabilityTitle ||
  100. !submission.data.categoryTitle ||
  101. !submission.data.competencyTitle
  102. ) {
  103. throw new Error("Mustn't publish submission without an RDF mapping!");
  104. }
  105. submission.data.type === "COURSE" ?
  106. await courseService.addNewCourse(submission.data) :
  107. await videoService.addNewVideo(submission.data);
  108. submission.status = "published";
  109. await updateSubmission(submission);
  110. };
  111. /**
  112. * Insert new submission in the database and send
  113. * tracking link to user if an email was provided.
  114. * @param {String} type course / video
  115. * @param {String} title
  116. * @param {String} hyperlink
  117. * @param {String} email
  118. */
  119. const insertNewSubmission = async (type, title, hyperlink, email) => {
  120. const timestamp = (new Date()).toUTCString();
  121. const id = generateSubmissionID(email, timestamp);
  122. const insertionResult = await submissionRepo.insert({
  123. id,
  124. status: "processing",
  125. submitter: email ? email : "anonymous",
  126. data: {timestamp, type, title, hyperlink},
  127. });
  128. if (email) {
  129. mail.sendEmail(
  130. process.env.SUBMISSION_EMAIL_ADDRESS,
  131. email,
  132. "Your RDFmapped Content Submission",
  133. `Thank you very much for submitting content to the RDFmapped website, we really appreciate the contribution.\n` +
  134. `You can track the progress of you submission via the following link: https://rdfmapped.com/submission/${id}`,
  135. `<p>Thank you very much for submitting content to the RDFmapped website, we really appreciate the contribution.</p>` +
  136. `You can track the progress of you submission via the following link: ` +
  137. `<a href="https://rdfmapped.com/submission/${id}" target="_blank" rel="noopener noreferrer">` +
  138. `https://rdfmapped.com/submission/${id}</a>`);
  139. }
  140. await attemptToFindURN(insertionResult.rows[0]);
  141. };
  142. /**
  143. * Verify the integrity of a submission via the LinkedIn
  144. * Learning API and update its status accordingly.
  145. * Upon success, the submission's Unique Resource Identifier
  146. * is set to match the LinkedIn Learning URN.
  147. * @param {Object} submission
  148. */
  149. const attemptToFindURN = async (submission) => {
  150. try {
  151. log.info("Attempting to find URN for submission(%s)", submission.id);
  152. const result = await linkedinAPI.fetchURNByContent(submission, submission.data.type);
  153. if (result) {
  154. log.info("Found URN(%s) for submission(%s)", result, submission.id);
  155. submission.data.urn = result;
  156. }
  157. submission.status = result ? "pending" : "failed";
  158. await updateSubmission(submission);
  159. } catch (error) {
  160. /* istanbul ignore next */
  161. log.error("Finding URN for submission(%s) failed, err: " +
  162. error.message, submission.id);
  163. }
  164. };
  165. /**
  166. * Update a submission object in the database.
  167. * @param {Object} submission
  168. */
  169. const updateSubmission = async (submission) => {
  170. await submissionRepo.update(submission);
  171. };
  172. module.exports = {
  173. fetchById,
  174. insertNewSubmission,
  175. fetchAll,
  176. rejectSubmission,
  177. publishSubmission,
  178. mapSubmission,
  179. };