Source: routers/sheetMusic.js

const express = require('express');
const auth = require('../middleware/authenticator.js');
const verify = require("../middleware/verifier.js");
const mysql = require("../db/mysql.js");
const router = express.Router();
const alphaProcesser = require('../AlphaTab/ProcessAlphaTab.js');
const ParserPData = require("../Parser/ParserPerformanceData.js");
const Manipulator = require("../Parser/ParsedOutputManipulator.js");

/**
 * @typedef {object} SheetMusicGetPackage
 * @property {SheetMusicGetReturnPackage[]} sheet_music Information about each piece of sheet music
 */

/**
 * @typedef {object} SheetMusicGetReturnPackage
 * @property {string} sheet_music_id The ID of the sheet music
 * @property {string} title The title of the sheet music
 * @property {string} composer_names Metadata about the creators of the sheet music
 */

/**
 * Route for getting all sheet music for the choir matching the given choir ID
 * @name sheet-music/get
 * @function
 * @inner
 * @param {*} req
 * @param {string} req.query.choirId The choir ID to be accessed.
 * @param {*} res
 * @returns {SheetMusicGetPackage|string} Information about the sheet music retrieved. Otherwise, an error message if an error occured
 * @async
 */
router.get('/', auth.token, async (req, res) => {
  verify.userIsVerified(req,res).then((isVerified) => {
    if (!isVerified) {
      res.status(403).send('Unauthorized');
    } else {
      let choirId = req.query.choirId;
      const errorMessage = "Unable to get sheet music";
      if (choirId) {
        mysql.getClient().then((client) => {
          client.query("SELECT hex(sheet_music_id) AS sheet_music_id, title, composer_names FROM tma.sheet_music INNER JOIN tma.choir ON choir.choir_id = sheet_music.choir_id WHERE choir.choir_id = unhex(?)", [choirId], function(error, results, fields) {
            if (error) {
              res.status(503).send(errorMessage);
            } else {
              let retObj = {"sheet_music":results};
              res.status(200).json(retObj);
            }
          });
        });
      } else {
        res.status(400).send(errorMessage);
      }
    }
  }).catch((error) => {
    res.status(403).send('Unauthorized');
  });
});

/**
 * @typedef {object} SheetMusicGetSpecificPackage
 * @property {string} sheet_music The AlphaTex of the sheet music
 * @property {string[]} part_list A list of the part (i.e. track) names
 * @property {string[]} clefs The clefs per staff
 * @property {string} part If not null, then the part of the current user in the sheet music
 */

/**
 * Route for getting performance data and the AlphaTex for a specific piece of sheet music
 * @name sheet-music/get/specific
 * @function
 * @inner
 * @param {*} req
 * @param {string} req.query.sheetMusicId The sheet music ID to be accessed
 * @param {*} res
 * @returns {SheetMusicGetSpecificPackage|string} In-depth information about the requested sheet music. Otherwise, an error message if an error occured
 * @async
 */
router.get('/specific', auth.token, async (req, res) => {
  verify.memberOfSheetMusic(req, res).then((choirId) => {
    if (choirId === null) {
      res.status(403).send('Unauthorized');
    } else {
      let sheetMusicId = req.query.sheetMusicId;
      const errorMessage = "Unable to get specific sheet music";
      if (sheetMusicId) {
        mysql.getClient().then((client) => {
          client.query("SELECT encoding,performance_data FROM tma.sheet_music WHERE sheet_music_id = unhex(?)", [sheetMusicId], function(error, results, fields) {
            if (error) {
              res.status(503).send(errorMessage);
            } else {
              let performanceData = JSON.parse(results[0]['performance_data']);
              let partNames = ParserPData.getPartNames(performanceData);
              let clefs = ParserPData.getClefs(performanceData);
              let retObj = {"sheet_music":results[0]['encoding'], "part_list":partNames, "clefs":clefs};
  
              client.query("SELECT hex(member_id) AS member_id from tma.member WHERE choir_id=unhex(?) AND person_id=?", [choirId, res.locals.uid], function(error, results, fields) {
                if (error) {
                  res.status(503).send(errorMessage);
                } else {
                  let memberId = results[0]["member_id"];
                  client.query("SELECT part FROM tma.member_sheet_music_part WHERE sheet_music_id=unhex(?) AND member_id=unhex(?)", [sheetMusicId, memberId], function(error, results, fields) {
                    if (error) {
                      res.status(503).send(errorMessage);
                    } else {
                      if (results.length === 0) {
                        retObj.part = null;
                      } else {
                        retObj.part = results[0]["part"];
                      }
                      res.status(200).json(retObj);
                    }
                  });
                }
              });
            }
          });
        });
      } else {
        res.status(400).send(errorMessage);
      }
    }
  }).catch((error) => {
    res.status(403).send('Unauthorized');
  });
});

/**
 * @typedef {object} SheetMusicGetPartPackage
 * @property {number[]} performance_expectation A 1D array of even size with the ith index as the midi value and the i+1 index as the duration of that note for i%2==0
 * @property {number[]} lower_upper A 1D two valued array with the lower and upper midi values
 * @property {number[]} measure_lengths A collection of the lengths of each Measure. The ith index contains the length in seconds of the i+1 Measure
 */

/**
 * Route for getting performance information about a specific part in a specific piece of sheet music
 * @name sheet-music/get/part
 * @function
 * @inner
 * @param {*} req
 * @param {string} req.query.sheetMusicId The sheet music ID to be accessed.
 * @param {string} req.query.partName The name of the part to be retrieved
 * @param {*} res
 * @returns {SheetMusicGetPartPackage|string} In-depth information about the part in the requested sheet music. Otherwise, an error message if an error occured
 * @async
 */
router.get('/part', auth.token, async (req,res) => {
  verify.memberOfSheetMusic(req, res).then((choirId) => {
    if (choirId === null) {
      res.status(403).send('Unauthorized');
    } else {
      let sheetMusicId = req.query.sheetMusicId;
      let partName = req.query.partName;
      const errorMessage = "Unable to get part";
      if (sheetMusicId && partName) {
        mysql.getClient().then((client) => {
          client.query("SELECT performance_data FROM tma.sheet_music WHERE sheet_music_id = unhex(?)", [sheetMusicId], function(error, results, fields) {
            if (error) {
              res.status(503).send(errorMessage);
            } else {
              let performanceData = JSON.parse(results[0]['performance_data']);
              let noteStream = ParserPData.getNoteStream(performanceData, partName);
              let lowerAndUpper = ParserPData.getLowerAndUpper(noteStream);
              let manipulator = new Manipulator(performanceData);
              let measureLengths = manipulator.getMeasureTimeLength(partName);
              res.status(200).json({"performance_expectation":noteStream, "lower_upper":lowerAndUpper, "measure_lengths":measureLengths});
            }
          });
        });
      } else {
        res.status(400).send(errorMessage);
      }
    }
  }).catch((error) => {
    res.status(403).send('Unauthorized');
  });
});

/**
 * Route for adding a piece of sheet music
 * @name sheet-music/post
 * @function
 * @inner
 * @param {*} req
 * @param {string} req.body.choirId The choir ID to be accessed
 * @param {string} req.body.encoding The AlphaTex of the sheet music to be added
 * @param {string} req.body.composerNames Metadata about the creators of the sheet music
 * @param {string} req.body.choirId The ID of the choir to which to attach the sheet music
 * @param {object} req.body.lyrics Lyrics per staff as a space separated string for each lyric
 * @param {*} res
 * @returns {string} An error message if an error occured
 * @async
 */
router.post('/', auth.token, async (req,res) => {
  verify.admin(req,res).then((isVerified) => {
    if (!isVerified) {
      res.status(403).send('Unauthorized');
    } else {
      let texInput = req.body.encoding;
      let title = req.body.title;
      let composers = req.body.composerNames;
      let choirId = req.body.choirId;
      let lyricsMap = req.body.lyrics;
      const errorMessage = "Unable to add sheet music";
      if (texInput && title && composers && choirId && lyricsMap) {
        let parsedOutput = alphaProcesser.process(texInput, lyricsMap);
        mysql.getClient().then((client) => {
          client.query("insert into tma.sheet_music (sheet_music_id, encoding, title, composer_names, performance_data, person_id, choir_id) values(unhex(replace(uuid(),\"-\",\"\")), ?, ?, ?, ?, ?, unhex(?))", [texInput, title, composers, parsedOutput.toString(), res.locals.uid, choirId], function(error, results, fields) {
            if (error) {
              res.status(503).send(errorMessage);
            } else {
              res.status(200).send();
            }
          });
        });
      } else {
        res.status(400).send(errorMessage);
      }
    }
  }).catch((error) => {
    res.status(403).send('Unauthorized');
  });
});

module.exports = router;