diff --git a/config/config.js b/config/config.js index 440983e..f4d7dee 100644 --- a/config/config.js +++ b/config/config.js @@ -56,6 +56,7 @@ c.fs.compendium = path.join(c.fs.base, 'compendium'); c.fs.deleted = path.join(c.fs.base, 'deleted'); c.fs.job = path.join(c.fs.base, 'job'); c.fs.cache = path.join(c.fs.base, 'cache'); +c.fs.dns = path.join(c.fs.base, 'dns'); c.fs.delete_inc = true; c.fs.fail_on_no_files = yn(env.MUNCHER_FAIL_ON_NO_FILES || 'false'); @@ -88,6 +89,8 @@ c.user.level.view_candidates = 500; c.user.level.view_status = 1000; c.user.level.delete_compendium = 1000; c.user.level.manage_links = 500; +c.user.level.manage_publisher = 1000; +c.user.level.manage_journal = 500; // bagtainer configuration c.bagtainer = {}; @@ -148,7 +151,6 @@ c.bagtainer.docker.create_options = { Env: ['O2R_MUNCHER=true'], Memory: 4294967296, // 4G MemorySwap: 8589934592, // double of 4G - NetworkDisabled: true, User: env.MUNCHER_CONTAINER_USER || '1000' // user name depends on image, use id to be save }; c.bagtainer.rm = yn(env.EXECUTE_CONTAINER_RM || 'true'); @@ -167,7 +169,7 @@ c.email.sender = env.MUNCHER_EMAIL_SENDER; // template for sending emails //if (emailTransporter) { // let mail = { -// from: config.email.sender, // sender address +// from: config.email.sender, // sender address // to: config.email.receivers, // subject: '[o2r platform] something happened', // text: '...' @@ -293,6 +295,24 @@ c.substitution.docker.volume.mode = ":ro"; c.substitution.docker.cmd = 'docker run -it --rm'; c.substitution.docker.imageNamePrefix = 'erc:'; +c.dns = {}; +c.dns.dnsmasq = {}; +c.dns.priority = {}; +c.dns.dockerfile = "" + + "FROM alpine:edge\n" + + "RUN apk --no-cache add dnsmasq\n" + + "EXPOSE 53/tcp 53/udp\n" + + "COPY dnsmasq.conf /etc/dnsmasq.conf\n" + + "CMD [\"dnsmasq\", \"--no-daemon\"]"; +c.dns.dnsmasq.default = "" + + "log-queries\n" + + "no-hosts\n" + + "no-resolv\n" + + "cache-size=100000\n"; +c.dns.dnsmasq.filterDummy = "server=/"; +c.dns.priority.publisher = 100; +c.dns.priority.journal = 50; + c.checker = {}; c.checker.diffFileName = 'check.html'; diff --git a/controllers/compendium.js b/controllers/compendium.js index 951c96c..2cc9aea 100644 --- a/controllers/compendium.js +++ b/controllers/compendium.js @@ -39,784 +39,875 @@ const resolve_public_link = require('./link').resolve_public_link; const Compendium = require('../lib/model/compendium'); const User = require('../lib/model/user'); const Job = require('../lib/model/job'); +const Journal = require('../lib/model/journal'); /* * user must be owner (unless delete) OR have the sufficient level */ detect_rights = function (user_id, compendium, level) { - debug('[%s] Checking rights for user %s against level %s', compendium.id, user_id, level); - - return new Promise(function (resolve, reject) { - if (user_id === compendium.user && level < config.user.level.delete_compendium) { - debug('[%s] User %s is owner and requested level is not too high (%s < %s)', compendium.id, user_id, level, config.user.level.delete_compendium); - resolve({ user_has_rights: true, user_id: user_id }); - } else { - // user is not author but could have required level - debug('[%s] User %s trying to access compendium by user %s', compendium.id, user_id, compendium.user); - - User.findOne({ orcid: user_id }, (err, user) => { - if (err) { - reject({ error: 'problem retrieving user information: ' + err }); + debug('[%s] Checking rights for user %s against level %s', compendium.id, user_id, level); + + return new Promise(function (resolve, reject) { + if (user_id === compendium.user && level < config.user.level.delete_compendium) { + debug('[%s] User %s is owner and requested level is not too high (%s < %s)', compendium.id, user_id, level, config.user.level.delete_compendium); + resolve({user_has_rights: true, user_id: user_id}); } else { - if (user.level >= level) { - debug('[%] User %s has level (%s), continuing ...', compendium.id, user_id, user.level); - resolve({ user_has_rights: true }); - } else { - reject({ error: 'not authorized to edit/view ' + compendium.id }); - } + // user is not author but could have required level + debug('[%s] User %s trying to access compendium by user %s', compendium.id, user_id, compendium.user); + + User.findOne({orcid: user_id}, (err, user) => { + if (err) { + reject({error: 'problem retrieving user information: ' + err}); + } else { + if (user.level >= level) { + debug('[%] User %s has level (%s), continuing ...', compendium.id, user_id, user.level); + resolve({user_has_rights: true}); + } else { + reject({error: 'not authorized to edit/view ' + compendium.id}); + } + } + }); } - }); - } - }); + }); } exports.viewCompendium = (req, res) => { - debug('[%s] view single compendium', req.params.id); - - resolve_public_link(req.params.id, (ident) => { - let id = null; - if (ident.is_link) { - id = ident.link; - } else { - id = ident.compendium; - } - - Compendium - .findOne({ id: ident.compendium }) - .select('id user metadata created candidate bag compendium substituted') - .lean() - .exec((err, compendium) => { - // eslint-disable-next-line no-eq-null, eqeqeq - if (err || compendium == null) { - debug('[%s] compendium does not exist! identifiers: %o', id, ident); - res.status(404).send({ error: 'no compendium with this id' }); - } else { - debug('[%s] single compendium found!', id); - - let answer = { - id: id, - metadata: compendium.metadata, - created: compendium.created, - user: compendium.user, - bag: compendium.bag, - compendium: compendium.compendium, - substituted: compendium.substituted - } + debug('[%s] view single compendium', req.params.id); - try { - fullPath = path.join(config.fs.compendium, ident.compendium); - fs.accessSync(fullPath); // throws if does not exist - answer.files = rewriteTree.rewriteTree(dirTree(fullPath), - fullPath.length, // remove local fs path and id - urlJoin(config.api.resource.compendium, id, config.api.sub_resource.data) // prepend proper location - ); - answer.files.name = id; - } catch (err) { - debug('[%s] Error: No data files found (Fail? %s): %s', id, config.fs.fail_on_no_files, err); - if (config.fs.fail_on_no_files) { - res.status(500).send({ error: 'internal error: could not read compendium contents from storage' }); - return; - } else { - answer.filesMissing = true; - } + resolve_public_link(req.params.id, (ident) => { + let id = null; + if (ident.is_link) { + id = ident.link; + } else { + id = ident.compendium; } - // check if user is allowed to view the candidate, not coming from a link - // (easier to check async if done after answer creation) - if (compendium.candidate && !ident.is_link) { - debug('[%s] Compendium is a candidate, need to make some checks.', id); - - if (!req.isAuthenticated()) { - debug('[%s] User is not authenticated, cannot view candidate.', id); - res.status(401).send({ error: 'user is not authenticated' }); - return; - } - detect_rights(req.user.orcid, compendium, config.user.level.view_candidates) - .then((passon) => { - if (passon.user_has_rights) { - debug('[%s] User %s may see candidate.', id, req.user.orcid); - answer.candidate = compendium.candidate; - - res.status(200).send(answer); - } else { - debug('[%s] Error: user does not have rights but promise fulfilled', id); - res.status(500).send({error: 'illegal state'}); - } - }, function (passon) { - debug('[%s] User %s may NOT see candidate.', id, req.user.orcid); - res.status(401).send(passon); + Compendium + .findOne({id: ident.compendium}) + .select('id user metadata created candidate bag compendium substituted') + .lean() + .exec((err, compendium) => { + // eslint-disable-next-line no-eq-null, eqeqeq + if (err || compendium == null) { + debug('[%s] compendium does not exist! identifiers: %o', id, ident); + res.status(404).send({error: 'no compendium with this id'}); + } else { + debug('[%s] single compendium found!', id); + + let answer = { + id: id, + metadata: compendium.metadata, + created: compendium.created, + user: compendium.user, + bag: compendium.bag, + compendium: compendium.compendium, + substituted: compendium.substituted + } + + try { + fullPath = path.join(config.fs.compendium, ident.compendium); + fs.accessSync(fullPath); // throws if does not exist + answer.files = rewriteTree.rewriteTree(dirTree(fullPath), + fullPath.length, // remove local fs path and id + urlJoin(config.api.resource.compendium, id, config.api.sub_resource.data) // prepend proper location + ); + answer.files.name = id; + } catch (err) { + debug('[%s] Error: No data files found (Fail? %s): %s', id, config.fs.fail_on_no_files, err); + if (config.fs.fail_on_no_files) { + res.status(500).send({error: 'internal error: could not read compendium contents from storage'}); + return; + } else { + answer.filesMissing = true; + } + } + + // check if user is allowed to view the candidate, not coming from a link + // (easier to check async if done after answer creation) + if (compendium.candidate && !ident.is_link) { + debug('[%s] Compendium is a candidate, need to make some checks.', id); + + if (!req.isAuthenticated()) { + debug('[%s] User is not authenticated, cannot view candidate.', id); + res.status(401).send({error: 'user is not authenticated'}); + return; + } + detect_rights(req.user.orcid, compendium, config.user.level.view_candidates) + .then((passon) => { + if (passon.user_has_rights) { + debug('[%s] User %s may see candidate.', id, req.user.orcid); + answer.candidate = compendium.candidate; + + res.status(200).send(answer); + } else { + debug('[%s] Error: user does not have rights but promise fulfilled', id); + res.status(500).send({error: 'illegal state'}); + } + }, function (passon) { + debug('[%s] User %s may NOT see candidate.', id, req.user.orcid); + res.status(401).send(passon); + }); + } else { + res.status(200).send(answer); + } + } }); - } else { - res.status(200).send(answer); - } - } }); - }); }; exports.deleteCompendium = (req, res) => { - let id = req.params.id; - debug('[%s] DELETE compendium', id); + let id = req.params.id; + debug('[%s] DELETE compendium', id); + + Compendium.findOne({id: id}).select('id user candidate').exec((err, compendium) => { + // eslint-disable-next-line no-eq-null, eqeqeq + if (err || compendium == null) { + debug('[%s] compendium does not exist!', id); + res.status(404).send({error: 'no compendium with this id'}); + } else { + let compendium_path = path.join(config.fs.compendium, id); - Compendium.findOne({ id: id }).select('id user candidate').exec((err, compendium) => { - // eslint-disable-next-line no-eq-null, eqeqeq - if (err || compendium == null) { - debug('[%s] compendium does not exist!', id); - res.status(404).send({ error: 'no compendium with this id' }); - } else { - let compendium_path = path.join(config.fs.compendium, id); - - if (!req.isAuthenticated()) { - debug('[%s] User is not authenticated, cannot even view candidate.', id); - res.status(401).send({ error: 'user is not authenticated' }); - return; - } - - // check if user is allowed to delete the compendium or candidate - if (compendium.candidate) { - // compendium is a candidate - detect_rights(req.user.orcid, compendium, config.user.level.view_candidates) - .then((passon) => { - if (passon.user_has_rights) { - debug('[%s] single compendium candidate found, going to remove it and its files at %s on behalf of user %s (has rights: %s)', - compendium.id, compendium_path, passon.user_id, passon.user_has_rights); - Compendium.findOneAndRemove({ id: compendium.id }).exec((err) => { - if (err) { - debug('[%s] error deleting compendium: %s', compendium.id, err); - res.status(500).send({ error: err.message }); - } else { - fse.remove(compendium_path, (err) => { - if (err) { - debug('[%s] Error deleting data files: %s', compendium.id, err); - res.status(500).send({ error: err.message }); - } else { - debug('[%s] Deleted!', compendium.id); - res.sendStatus(204); - } - }); - }; - }); - } else { - debug('[%s] Error: user does not have rights but promise fulfilled', id); - res.status(500); - } - }, function (passon) { - debug('[%s] User %s may NOT delete candidate.', id, req.user.orcid); - res.status(403).send(passon); - }); - } else { - // compendium is NOT a candidate - detect_rights(req.user.orcid, compendium, config.user.level.delete_compendium) - .then((passon) => { - if (passon.user_has_rights) { - debug('[%s] single compendium found, going to remove it and its files at %s on behalf of user %s (has rights: %s)', - compendium.id, compendium_path, passon.user_id, passon.user_has_rights); - - // save compendium metadata to file - Compendium.findOne({ id: id }).exec((err, compendium) => { - // eslint-disable-next-line no-eq-null, eqeqeq - if (err || compendium == null) { - debug('[%s] compendium does not exist!', id); - res.status(500); - } else { - metadata_file = path.join(config.fs.deleted, compendium.id + '.json'); - fs.writeFile(metadata_file, JSON.stringify(compendium), (err) => { - if (err) throw err; - debug('[%s] Saved full compendium metadata to %s', metadata_file); - - Compendium.findOneAndRemove({ id: compendium.id }).exec((err) => { - if (err) { - debug('[%s] error deleting compendium: %s', compendium.id, err); - res.status(500).send({ error: err.message }); - } else { - deleted_path = path.join(config.fs.deleted, compendium.id); - debug('[%s] Moving compendium files to %s', compendium.id, deleted_path); - fse.move(compendium_path, deleted_path, (err) => { - if (err) { - debug('[%s] Error moving data files to %s: %s', compendium.id, deleted_path, err); - res.status(500).send({ error: err.message }); - } else { - res.sendStatus(204); - } - }); - }; + if (!req.isAuthenticated()) { + debug('[%s] User is not authenticated, cannot even view candidate.', id); + res.status(401).send({error: 'user is not authenticated'}); + return; + } + + // check if user is allowed to delete the compendium or candidate + if (compendium.candidate) { + // compendium is a candidate + detect_rights(req.user.orcid, compendium, config.user.level.view_candidates) + .then((passon) => { + if (passon.user_has_rights) { + debug('[%s] single compendium candidate found, going to remove it and its files at %s on behalf of user %s (has rights: %s)', + compendium.id, compendium_path, passon.user_id, passon.user_has_rights); + Compendium.findOneAndRemove({id: compendium.id}).exec((err) => { + if (err) { + debug('[%s] error deleting compendium: %s', compendium.id, err); + res.status(500).send({error: err.message}); + } else { + fse.remove(compendium_path, (err) => { + if (err) { + debug('[%s] Error deleting data files: %s', compendium.id, err); + res.status(500).send({error: err.message}); + } else { + debug('[%s] Deleted!', compendium.id); + // Delete also entry at journal + debug('[%s] Also remove compendium entry from journal if needed', compendium.id); + Journal.findOne({compandia: compendium.id}, (err, journal) => { + if (err) + debug('[%s] Error getting journal from database: %s', compendium.id, err); + else if (!journal) + debug('[%s] Compendium has no entry at any journal', compendium.id); + else { + journal.domains.splice(journal.domains.indexOf(domains[0]), 1).sort(); + journal.save(err => { + if (err) + debug('[%s] Error saving updated journal: %s', compendium.id, err); + else + debug('[%s] Successfully removed compendium from journal', compendium.id); + }); + res.sendStatus(204); + } + }); + } + }); + } + + }); + } else { + debug('[%s] Error: user does not have rights but promise fulfilled', id); + res.status(500); + } + }, function (passon) { + debug('[%s] User %s may NOT delete candidate.', id, req.user.orcid); + res.status(403).send(passon); }); - }); - } - }); } else { - debug('[%s] Error: user does not have rights but promise fulfilled', id); - res.status(500); + // compendium is NOT a candidate + detect_rights(req.user.orcid, compendium, config.user.level.delete_compendium) + .then((passon) => { + if (passon.user_has_rights) { + debug('[%s] single compendium found, going to remove it and its files at %s on behalf of user %s (has rights: %s)', + compendium.id, compendium_path, passon.user_id, passon.user_has_rights); + + // save compendium metadata to file + Compendium.findOne({id: id}).exec((err, compendium) => { + // eslint-disable-next-line no-eq-null, eqeqeq + if (err || compendium == null) { + debug('[%s] compendium does not exist!', id); + res.status(500); + } else { + metadata_file = path.join(config.fs.deleted, compendium.id + '.json'); + fs.writeFile(metadata_file, JSON.stringify(compendium), (err) => { + if (err) throw err; + debug('[%s] Saved full compendium metadata to %s', metadata_file); + + Compendium.findOneAndRemove({id: compendium.id}).exec((err) => { + if (err) { + debug('[%s] error deleting compendium: %s', compendium.id, err); + res.status(500).send({error: err.message}); + } else { + deleted_path = path.join(config.fs.deleted, compendium.id); + debug('[%s] Moving compendium files to %s', compendium.id, deleted_path); + fse.move(compendium_path, deleted_path, (err) => { + if (err) { + debug('[%s] Error moving data files to %s: %s', compendium.id, deleted_path, err); + res.status(500).send({error: err.message}); + } else { + res.sendStatus(204); + } + }); + } + ; + }); + }); + } + }); + } else { + debug('[%s] Error: user does not have rights but promise fulfilled', id); + res.status(500); + } + }, function (passon) { + debug('[%s] Compendium is NOT a candidate, can NOT be deleted by user %s.', id, req.user.orcid); + res.status(400).send({error: 'compendium is not a candidate, cannot be deleted'}); + }); } - }, function (passon) { - debug('[%s] Compendium is NOT a candidate, can NOT be deleted by user %s.', id, req.user.orcid); - res.status(400).send({ error: 'compendium is not a candidate, cannot be deleted' }); - }); - } - }; - }); + } + ; + }); }; exports.viewCompendiumJobs = (req, res) => { - debug('[%s] view compendium jobs', req.params.id); - - resolve_public_link(req.params.id, (ident) => { - if (ident.is_link) { - id = ident.link; - } else { - id = ident.compendium; - } + debug('[%s] view compendium jobs', req.params.id); - Compendium.findOne({ id: ident.compendium }).select('id candidate user').lean().exec((err, compendium) => { - if (err) { - debug('[%id] Error finding compendium for job: %s', ident.compendium, err.message); - res.status(404).send({ error: 'error finding compendium: ' + err.message }); - } else { - if (compendium == null) { - debug('[%id] Compendium does not exist, cannot return jobs', ident.compendium); - res.status(404).send({ error: 'no compendium with id ' + id }); - return; - } - - if (compendium.candidate && !ident.is_link) { - debug('[%id] Compendium does exist, but is a candidate not accessed via public link, not exposing jobs nor compendium: %o', ident.compendium, ident); - res.status(404).send({ error: 'compendium not found' }); - return; - } - - var filter = {}; - if (compendium.candidate && ident.is_link) { - debug('[%s] Accessing jobs of candidate via public link: %o', ident.compendium, ident); - filter = { compendium_id: ident.link }; + resolve_public_link(req.params.id, (ident) => { + if (ident.is_link) { + id = ident.link; } else { - filter = { compendium_id: ident.compendium }; + id = ident.compendium; } - - var answer = {}; - var limit = parseInt(req.query.limit || config.list_limit, 10); - var start = parseInt(req.query.start || 1, 10) - 1; - - Job - .find(filter) - .select('id') - .skip(start) - .limit(limit) - .lean() - .exec((err, jobs) => { + + Compendium.findOne({id: ident.compendium}).select('id candidate user').lean().exec((err, compendium) => { if (err) { - res.status(500).send({ error: 'query failed' }); + debug('[%id] Error finding compendium for job: %s', ident.compendium, err.message); + res.status(404).send({error: 'error finding compendium: ' + err.message}); } else { - - answer.results = jobs.map(job => { - return job.id; - }); - - res.status(200).send(answer); + if (compendium == null) { + debug('[%id] Compendium does not exist, cannot return jobs', ident.compendium); + res.status(404).send({error: 'no compendium with id ' + id}); + return; + } + + if (compendium.candidate && !ident.is_link) { + debug('[%id] Compendium does exist, but is a candidate not accessed via public link, not exposing jobs nor compendium: %o', ident.compendium, ident); + res.status(404).send({error: 'compendium not found'}); + return; + } + + var filter = {}; + if (compendium.candidate && ident.is_link) { + debug('[%s] Accessing jobs of candidate via public link: %o', ident.compendium, ident); + filter = {compendium_id: ident.link}; + } else { + filter = {compendium_id: ident.compendium}; + } + + var answer = {}; + var limit = parseInt(req.query.limit || config.list_limit, 10); + var start = parseInt(req.query.start || 1, 10) - 1; + + Job + .find(filter) + .select('id') + .skip(start) + .limit(limit) + .lean() + .exec((err, jobs) => { + if (err) { + res.status(500).send({error: 'query failed'}); + } else { + + answer.results = jobs.map(job => { + return job.id; + }); + + res.status(200).send(answer); + } + }); } - }); - } + }); }); - }); }; exports.listCompendia = (req, res) => { - let filter = {}; - - // eslint-disable-next-line no-eq-null, eqeqeq - if (req.query.job_id != null) { - filter.job_id = req.query.job_id; - } - // eslint-disable-next-line no-eq-null, eqeqeq - if (req.query.user != null) { - filter.user = req.query.user; - } - // eslint-disable-next-line no-eq-null, eqeqeq - if (req.query.doi != null) { - //only look at the o2r.identifier.doi field - filter[config.meta.doiPath] = req.query.doi; - } - - let search = { - limit: parseInt(req.query.limit || config.list_limit, 10), - start: parseInt(req.query.start || 1, 10) - 1, - filter: filter - }; - - let findCompendia = (passon) => { - // do not show candidates by default - passon.filter.candidate = false; + let filter = {}; - return new Promise(function (resolve, reject) { - Compendium - .find(passon.filter) - .select('id') - .skip(passon.start) - .limit(passon.limit) - .lean() - .exec((err, comps) => { - if (err) { - debug('Error querying candidates for user %s: %s', req.user.orcid, err); - let error = new Error('query failed'); - error.status = 500; - reject(error); - } else { - var count = comps.length; - if (count <= 0) { - debug('Search turned up empty, no compendium found.'); - } + // eslint-disable-next-line no-eq-null, eqeqeq + if (req.query.job_id != null) { + filter.job_id = req.query.job_id; + } + // eslint-disable-next-line no-eq-null, eqeqeq + if (req.query.user != null) { + filter.user = req.query.user; + } + // eslint-disable-next-line no-eq-null, eqeqeq + if (req.query.doi != null) { + //only look at the o2r.identifier.doi field + filter[config.meta.doiPath] = req.query.doi; + } - passon.results = comps.map(comp => { - return comp.id; - }); + let search = { + limit: parseInt(req.query.limit || config.list_limit, 10), + start: parseInt(req.query.start || 1, 10) - 1, + filter: filter + }; - resolve(passon); - } - }) - }); - }; + let findCompendia = (passon) => { + // do not show candidates by default + passon.filter.candidate = false; + + return new Promise(function (resolve, reject) { + Compendium + .find(passon.filter) + .select('id') + .skip(passon.start) + .limit(passon.limit) + .lean() + .exec((err, comps) => { + if (err) { + debug('Error querying candidates for user %s: %s', req.user.orcid, err); + let error = new Error('query failed'); + error.status = 500; + reject(error); + } else { + var count = comps.length; + if (count <= 0) { + debug('Search turned up empty, no compendium found.'); + } + + passon.results = comps.map(comp => { + return comp.id; + }); - // additionally, add the user's candidates if he requests compendia for himself as the first results - let findCandidates = (passon) => { - return new Promise(function (resolve, reject) { - if (req.query.user != null && req.isAuthenticated() && req.user.orcid === req.query.user) { - debug('User %s requests compendia for herself (%s), so pre-pending candidates to the response.', req.user.orcid, req.query.user); - passon.filter.candidate = true; - - Compendium.find(passon.filter).select('id').skip(passon.start).limit(passon.limit).exec((err, comps) => { - if (err) { - debug('Error querying candidates for user %s: %s', req.user.orcid, err); - let error = new Error('query failed'); - error.status = 500; - reject(error); - } else { - var count = comps.length; - if (count <= 0) { - debug('User %s has no candidates', req.user.orcid); - resolve(passon); + resolve(passon); + } + }) + }); + }; + + // additionally, add the user's candidates if he requests compendia for himself as the first results + let findCandidates = (passon) => { + return new Promise(function (resolve, reject) { + if (req.query.user != null && req.isAuthenticated() && req.user.orcid === req.query.user) { + debug('User %s requests compendia for herself (%s), so pre-pending candidates to the response.', req.user.orcid, req.query.user); + passon.filter.candidate = true; + + Compendium.find(passon.filter).select('id').skip(passon.start).limit(passon.limit).exec((err, comps) => { + if (err) { + debug('Error querying candidates for user %s: %s', req.user.orcid, err); + let error = new Error('query failed'); + error.status = 500; + reject(error); + } else { + var count = comps.length; + if (count <= 0) { + debug('User %s has no candidates', req.user.orcid); + resolve(passon); + } else { + debug('Adding %s candidates to the response for user.', req.user.orcid); + + passon.candidates = comps.map(comp => { + return comp.id; + }); + + resolve(passon); + } + } + }); } else { - debug('Adding %s candidates to the response for user.', req.user.orcid); + resolve(passon); + } + }); + }; - passon.candidates = comps.map(comp => { - return comp.id; - }); + findCompendia(search) + .then(findCandidates) + .then(passon => { + debug('Completed search, returning %s compendia plus %s candidates.', passon.results.length, ((passon.candidates) ? passon.candidates.length : '0')); - resolve(passon); + let answer = {}; + if (passon.candidates) { + answer.results = passon.candidates.concat(passon.results); + } else { + answer.results = passon.results; + } + res.status(200).send(answer); + }) + .catch(err => { + debug('Rejection during search: %o', err); + let status = 500; + if (err.status) { + status = err.status; } - } + res.status(status).send({error: err.message}); }); - } else { - resolve(passon); - } - }); - }; - - findCompendia(search) - .then(findCandidates) - .then(passon => { - debug('Completed search, returning %s compendia plus %s candidates.', passon.results.length, ((passon.candidates) ? passon.candidates.length : '0')); - - let answer = {}; - if (passon.candidates) { - answer.results = passon.candidates.concat(passon.results); - } else { - answer.results = passon.results; - } - res.status(200).send(answer); - }) - .catch(err => { - debug('Rejection during search: %o', err); - let status = 500; - if (err.status) { - status = err.status; - } - res.status(status).send({ error: err.message }); - }); }; exports.viewCompendiumMetadata = (req, res) => { - let id = req.params.id; - let answer = { id: id }; - - Compendium - .findOne({ id }) - .select('id metadata candidate user') - .lean() - .exec((err, compendium) => { - // eslint-disable-next-line no-eq-null, eqeqeq - if (err || compendium == null) { - res.status(404).send({ error: 'no compendium with this id' }); - } else { - answer.metadata = {}; - answer.metadata.o2r = compendium.metadata.o2r; - - // check if user is allowed to view the candidate (easier to check async if done after answer creation) - if (compendium.candidate) { - debug('[%s] Compendium is a candidate, need to make some checks.', id); - - if (!req.isAuthenticated()) { - debug('[%s] User is not authenticated, cannot view candidate.', id); - res.status(401).send({ error: 'user is not authenticated' }); - return; - } - detect_rights(req.user.orcid, compendium, config.user.level.view_candidates) - .then((passon) => { - if (passon.user_has_rights) { - debug('[%s] User %s may see candidate metadata.', id, req.user.orcid); - res.status(200).send(answer); - } else { - debug('[%s] Error: user does not have rights but promise fulfilled', id); - res.status(500).send({error: 'illegal state'}); - } - }, function (passon) { - debug('[%s] User %s may NOT see candidate metadata.', id, req.user.orcid); - res.status(403).send(passon); - }); - } else { - res.status(200).send(answer); - } - } - }); + let id = req.params.id; + let answer = {id: id}; + + Compendium + .findOne({id}) + .select('id metadata candidate user') + .lean() + .exec((err, compendium) => { + // eslint-disable-next-line no-eq-null, eqeqeq + if (err || compendium == null) { + res.status(404).send({error: 'no compendium with this id'}); + } else { + answer.metadata = {}; + answer.metadata.o2r = compendium.metadata.o2r; + + // check if user is allowed to view the candidate (easier to check async if done after answer creation) + if (compendium.candidate) { + debug('[%s] Compendium is a candidate, need to make some checks.', id); + + if (!req.isAuthenticated()) { + debug('[%s] User is not authenticated, cannot view candidate.', id); + res.status(401).send({error: 'user is not authenticated'}); + return; + } + detect_rights(req.user.orcid, compendium, config.user.level.view_candidates) + .then((passon) => { + if (passon.user_has_rights) { + debug('[%s] User %s may see candidate metadata.', id, req.user.orcid); + res.status(200).send(answer); + } else { + debug('[%s] Error: user does not have rights but promise fulfilled', id); + res.status(500).send({error: 'illegal state'}); + } + }, function (passon) { + debug('[%s] User %s may NOT see candidate metadata.', id, req.user.orcid); + res.status(403).send(passon); + }); + } else { + res.status(200).send(answer); + } + } + }); }; // overwrite metadata file in compendium directory (which is then used for brokering) updateMetadataFile = function (id, file, metadata) { - return new Promise((fulfill, reject) => { - debug('[%s] Overwriting file %s', id, file); - fs.truncate(file, 0, function () { - fs.writeFile(file, JSON.stringify(metadata, null, config.meta.prettyPrint.indent), function (err) { - if (err) { - debug('[%s] Error updating normative metadata file: %s', id, err); - err.message = 'Error updating normative metadata file'; - reject(err); - } else { - fulfill({ - id: id, - file: file - }); - } - }); + return new Promise((fulfill, reject) => { + debug('[%s] Overwriting file %s', id, file); + fs.truncate(file, 0, function () { + fs.writeFile(file, JSON.stringify(metadata, null, config.meta.prettyPrint.indent), function (err) { + if (err) { + debug('[%s] Error updating normative metadata file: %s', id, err); + err.message = 'Error updating normative metadata file'; + reject(err); + } else { + fulfill({ + id: id, + file: file + }); + } + }); + }); }); - }); } // overwrite main and display file settings in compendium configuration file updateConfigurationFile = function (compendium) { - return new Promise((fulfill, reject) => { - let file; - if (bagit.compendiumIsBag(compendium.id)) - file = path.join(config.fs.compendium, compendium.id, config.bagit.payloadDirectory, config.bagtainer.configFile.name); - else - file = path.join(config.fs.compendium, compendium.id, config.bagtainer.configFile.name); - - fs.stat(file, (err, stats) => { - if (err) { - debug('[%s] Configuration file %s does not exist, not updating anything', compendium.id, file); - fulfill(); - } else { - debug('[%s] Updating file %s', compendium.id, file); - - try { - let configuration = yaml.load(file); - - // save main and display file (make paths relative to payload dir, - // because metadata extraction or updates from UI use the bagit root dir - // as the starting point) - let payloadDir; - if (bagit.compendiumIsBag(compendium.id)) - payloadDir = path.join(config.fs.compendium, compendium.id, config.bagit.payloadDirectory); - else - payloadDir = path.join(config.fs.compendium, compendium.id); - - // could add a check here for existance of mainfile and displayfile, but better solution is https://github.com/o2r-project/o2r-meta/issues/94 - fullMain = path.join(config.fs.compendium, compendium.id, get(compendium, config.bagtainer.mainFilePath)); - fullDisplay = path.join(config.fs.compendium, compendium.id, get(compendium, config.bagtainer.displayFilePath)); - - set(configuration, config.bagtainer.configFile.main_node, path.relative(payloadDir, fullMain)); - set(configuration, config.bagtainer.configFile.display_node, path.relative(payloadDir, fullDisplay)); - - // save licenses - set(configuration, config.bagtainer.configFile.licenses_node, get(compendium, config.bagtainer.licensesPath)); - - yamlWriter(file, configuration, function (err) { + return new Promise((fulfill, reject) => { + let file; + if (bagit.compendiumIsBag(compendium.id)) + file = path.join(config.fs.compendium, compendium.id, config.bagit.payloadDirectory, config.bagtainer.configFile.name); + else + file = path.join(config.fs.compendium, compendium.id, config.bagtainer.configFile.name); + + fs.stat(file, (err, stats) => { if (err) { - debug('[%s] Error saving file: %s', this.jobId, error); - reject(err); + debug('[%s] Configuration file %s does not exist, not updating anything', compendium.id, file); + fulfill(); } else { - fulfill({ - id: compendium.id, - file: file - }); + debug('[%s] Updating file %s', compendium.id, file); + + try { + let configuration = yaml.load(file); + + // save main and display file (make paths relative to payload dir, + // because metadata extraction or updates from UI use the bagit root dir + // as the starting point) + let payloadDir; + if (bagit.compendiumIsBag(compendium.id)) + payloadDir = path.join(config.fs.compendium, compendium.id, config.bagit.payloadDirectory); + else + payloadDir = path.join(config.fs.compendium, compendium.id); + + // could add a check here for existance of mainfile and displayfile, but better solution is https://github.com/o2r-project/o2r-meta/issues/94 + fullMain = path.join(config.fs.compendium, compendium.id, get(compendium, config.bagtainer.mainFilePath)); + fullDisplay = path.join(config.fs.compendium, compendium.id, get(compendium, config.bagtainer.displayFilePath)); + + set(configuration, config.bagtainer.configFile.main_node, path.relative(payloadDir, fullMain)); + set(configuration, config.bagtainer.configFile.display_node, path.relative(payloadDir, fullDisplay)); + + // save licenses + set(configuration, config.bagtainer.configFile.licenses_node, get(compendium, config.bagtainer.licensesPath)); + + yamlWriter(file, configuration, function (err) { + if (err) { + debug('[%s] Error saving file: %s', this.jobId, error); + reject(err); + } else { + fulfill({ + id: compendium.id, + file: file + }); + } + }); + } catch (err) { + debug('[%s] Error processing paths: %o', compendium.id, err); + reject(err); + } } - }); - } catch (err) { - debug('[%s] Error processing paths: %o', compendium.id, err); - reject(err); - } - } + }); }); - }); } reloadMetadataFromFile = function (id, metadata_file, targetElement) { - return new Promise((fulfill, reject) => { - // read mapped metadata for saving to DB - debug('[%s] Reading mapping file: %s', id, metadata_file); - fs.readFile(metadata_file, (err, data) => { - if (err) { - debug('[%s] Error reading mapping file: %s', id, err); - reject(err); - } else { - debug('[%s] Read file %s and stored contents to be saved at', id, metadata_file, targetElement); - fulfill({ - targetElement: targetElement, - metadata: JSON.parse(data), - file: metadata_file + return new Promise((fulfill, reject) => { + // read mapped metadata for saving to DB + debug('[%s] Reading mapping file: %s', id, metadata_file); + fs.readFile(metadata_file, (err, data) => { + if (err) { + debug('[%s] Error reading mapping file: %s', id, err); + reject(err); + } else { + debug('[%s] Read file %s and stored contents to be saved at', id, metadata_file, targetElement); + fulfill({ + targetElement: targetElement, + metadata: JSON.parse(data), + file: metadata_file + }); + } }); - } }); - }); } validateMetadata = function (compendium, metadata_file) { - return meta.validate(compendium.id, metadata_file); + return meta.validate(compendium.id, metadata_file); } brokerMetadata = function (compendium, metadata_dir, metadata_file, mappings) { - return new Promise((fulfill, reject) => { + return new Promise((fulfill, reject) => { - let brokerings = []; - Object.keys(mappings).forEach(function (name) { - brokerPromise = meta.broker(compendium.id, metadata_dir, metadata_file, name); - brokerings.push(brokerPromise); - }); - - Promise.all(brokerings) - .then((brokerResults) => { - debug('[%s] Completed brokerings: %s', compendium.id, brokerResults.filter(obj => { return !obj.error }).map(obj => { return obj.name; }).join(', ')); - debug('[%s] FAILED brokerings: %s', compendium.id, brokerResults.filter(obj => { return obj.error }).map(obj => { return obj.name; }).join(', ')); - - let reloads = []; - Object.keys(mappings).forEach((name) => { - mapping = mappings[name]; - reloadPromise = reloadMetadataFromFile(compendium.id, path.join(metadata_dir, mapping.file), mapping.targetElement); - reloads.push(reloadPromise); + let brokerings = []; + Object.keys(mappings).forEach(function (name) { + brokerPromise = meta.broker(compendium.id, metadata_dir, metadata_file, name); + brokerings.push(brokerPromise); }); - Promise.all(reloads) - .then((reloadResults) => { + Promise.all(brokerings) + .then((brokerResults) => { + debug('[%s] Completed brokerings: %s', compendium.id, brokerResults.filter(obj => { + return !obj.error + }).map(obj => { + return obj.name; + }).join(', ')); + debug('[%s] FAILED brokerings: %s', compendium.id, brokerResults.filter(obj => { + return obj.error + }).map(obj => { + return obj.name; + }).join(', ')); + + let reloads = []; + Object.keys(mappings).forEach((name) => { + mapping = mappings[name]; + reloadPromise = reloadMetadataFromFile(compendium.id, path.join(metadata_dir, mapping.file), mapping.targetElement); + reloads.push(reloadPromise); + }); - reloadResults.forEach((result) => { - objectPath.set(compendium.metadata, - result.targetElement, - result.metadata); + Promise.all(reloads) + .then((reloadResults) => { + + reloadResults.forEach((result) => { + objectPath.set(compendium.metadata, + result.targetElement, + result.metadata); + }); + debug('[%s] Reloaded metadata from %s files:', compendium.id, reloadResults.length, reloadResults.map(obj => { + return obj.file + }).join(', ')); + fulfill(compendium); + }); + }) + .catch(err => { + debug('[%s] Problem during metadata brokering: %s', compendium.id, err); + let errors = err.message.split(':'); + err.message = errorMessageHelper(errors[errors.length - 1]); + reject(err); }); - debug('[%s] Reloaded metadata from %s files:', compendium.id, reloadResults.length, reloadResults.map(obj => { return obj.file }).join(', ')); - fulfill(compendium); - }); - }) - .catch(err => { - debug('[%s] Problem during metadata brokering: %s', compendium.id, err); - let errors = err.message.split(':'); - err.message = errorMessageHelper(errors[errors.length - 1]); - reject(err); - }); - }); + }); } saveCompendium = function (compendium) { - return new Promise((fulfill, reject) => { - // FINALLY persist the metadata update to the database - compendium.markModified('metadata'); - compendium.save((err, doc) => { - if (err) { - debug('[%s] ERROR saving new compendium: %s', compendium.id, err); - reject(err); - } else { - debug('[%s] Updated compendium, now is: %o', compendium.id, doc); - fulfill(doc); - } + return new Promise((fulfill, reject) => { + // FINALLY persist the metadata update to the database + compendium.markModified('metadata'); + compendium.save((err, doc) => { + if (err) { + debug('[%s] ERROR saving new compendium: %s', compendium.id, err); + reject(err); + } else { + debug('[%s] Updated compendium, now is: %o', compendium.id, doc); + fulfill(doc); + } + }); }); - }); } exports.updateCompendiumMetadata = (req, res) => { - let id = req.params.id; - let answer = { id: id }; - - // check user - if (!req.isAuthenticated()) { - res.status(401).send({ error: 'user is not authenticated' }); - return; - } - let user_id = req.user.orcid; - - try { - Compendium.findOne({ id }).select('id metadata user').exec((err, compendium) => { - // eslint-disable-next-line no-eq-null, eqeqeq - if (err || compendium == null) { - res.status(404).send({ error: 'no compendium with this id' }); - } else { - detect_rights(user_id, compendium, config.user.level.edit_others) - .then(function (passon) { - if (!req.body.hasOwnProperty('o2r')) { - debug('[%s] invalid metadata provided: no o2r root element: %O', id, req.body); - res.status(422).send({ error: "JSON with root element 'o2r' required" }); - return; - } - - if (passon.user_has_rights) { - compendium.candidate = false; - compendium.markModified('candidate'); - } + let id = req.params.id; + let answer = {id: id}; - compendium.metadata.o2r = req.body.o2r; - answer.metadata = {}; - answer.metadata.o2r = compendium.metadata.o2r; + // check user + if (!req.isAuthenticated()) { + res.status(401).send({error: 'user is not authenticated'}); + return; + } + let user_id = req.user.orcid; - let compendium_path = path.join(config.fs.compendium, id); - let metadata_dir; - if (bagit.compendiumIsBag(id)) { - metadata_dir = path.join(compendium_path, config.bagit.payloadDirectory, config.meta.dir); + try { + Compendium.findOne({id}).select('id metadata user').exec((err, compendium) => { + // eslint-disable-next-line no-eq-null, eqeqeq + if (err || compendium == null) { + res.status(404).send({error: 'no compendium with this id'}); } else { - metadata_dir = path.join(compendium_path, config.meta.dir); + detect_rights(user_id, compendium, config.user.level.edit_others) + .then(function (passon) { + if (!req.body.hasOwnProperty('o2r')) { + debug('[%s] invalid metadata provided: no o2r root element: %O', id, req.body); + res.status(422).send({error: "JSON with root element 'o2r' required"}); + return; + } + + if (passon.user_has_rights) { + compendium.candidate = false; + compendium.markModified('candidate'); + } + + compendium.metadata.o2r = req.body.o2r; + answer.metadata = {}; + answer.metadata.o2r = compendium.metadata.o2r; + + let compendium_path = path.join(config.fs.compendium, id); + let metadata_dir; + if (bagit.compendiumIsBag(id)) { + metadata_dir = path.join(compendium_path, config.bagit.payloadDirectory, config.meta.dir); + } else { + metadata_dir = path.join(compendium_path, config.meta.dir); + } + + let normative_metadata_file = path.join(metadata_dir, config.meta.normativeFile); + + if (compendium.metadata && compendium.metadata.o2r) { + + updateMetadataFile(id, normative_metadata_file, compendium.metadata.o2r) + .then(() => { + return validateMetadata(compendium, normative_metadata_file); + }) + .then(() => { + return brokerMetadata(compendium, metadata_dir, normative_metadata_file, config.meta.broker.mappings); + }) + .then((updated_compendium) => { + return saveCompendium(updated_compendium); + }) + .then((updated_compendium) => { + return updateConfigurationFile(updated_compendium); + }) + .then(() => { + debug('[%s] Completed metadata update, sending answer.', id); + res.status(200).send(answer); + }) + .catch((err) => { + response = {error: 'Error updating metadata file, see log for details'}; + status = 500; + if (err.log) { + response.log = err.log; + status = 400; + } + debug('[%s] Error during metadata update, returning error: %o', id, response); + res.status(status).send(response); + }); + + } else { + debug('[%s] No metadata provided that could be brokered!', id); + res.status(500).send({error: 'Error updating metadata: no metadata found.'}); + } + }, function (passon) { + res.status(401).send(passon); + }); } + }); + } catch (err) { + debug('Internal error updating metadata: %O', err); + res.status(500).send({error: err.message}); + } +}; - let normative_metadata_file = path.join(metadata_dir, config.meta.normativeFile); +exports.viewPath = (req, res) => { + debug('View path %s', req.params.path); - if (compendium.metadata && compendium.metadata.o2r) { + resolve_public_link(req.params.id, (ident) => { + let id = null; + if (ident.is_link) { + id = ident.link; + } else { + id = ident.compendium; + } - updateMetadataFile(id, normative_metadata_file, compendium.metadata.o2r) - .then(() => { - return validateMetadata(compendium, normative_metadata_file); - }) - .then(() => { - return brokerMetadata(compendium, metadata_dir, normative_metadata_file, config.meta.broker.mappings); - }) - .then((updated_compendium) => { - return saveCompendium(updated_compendium); - }) - .then((updated_compendium) => { - return updateConfigurationFile(updated_compendium); - }) - .then(() => { - debug('[%s] Completed metadata update, sending answer.', id); - res.status(200).send(answer); - }) - .catch((err) => { - response = { error: 'Error updating metadata file, see log for details' }; - status = 500; - if (err.log) { - response.log = err.log; - status = 400; - } - debug('[%s] Error during metadata update, returning error: %o', id, response); - res.status(status).send(response); - }); + let size = req.query.size || null; + Compendium.findOne({id: ident.compendium}).select('id').lean().exec((err, compendium) => { + if (err || compendium == null) { + res.status(404).send({error: 'no compendium with this id'}); } else { - debug('[%s] No metadata provided that could be brokered!', id); - res.status(500).send({ error: 'Error updating metadata: no metadata found.' }); + let localPath = path.join(config.fs.compendium, ident.compendium, req.params.path); + try { + innerSend = function (response, filePath) { + mimetype = mime.lookup(filePath) || rewriteTree.extraMimeTypes(path.extname(filePath)); + response.type(mimetype).sendFile(filePath, {}, (err) => { + if (err) { + debug("Error viewing path: %o", err) + } else { + debug('Returned %s for %s as %s', filePath, req.params.path, mimetype); + } + }); + } + + debug('Accessing %s', localPath); + fs.accessSync(localPath); //throws if does not exist + if (size) { + resize(localPath, size, (finalPath, err, code) => { + if (err) { + let status = code || 500; + res.status(status).send({error: err}); + return; + } + + innerSend(res, finalPath); + }); + } else { + innerSend(res, localPath); + } + } catch (e) { + debug('Error accessing path: %s', e); + res.status(500).send({error: e.message}); + return; + } } - }, function (passon) { - res.status(401).send(passon); - }); - } + }); }); - } catch (err) { - debug('Internal error updating metadata: %O', err); - res.status(500).send({ error: err.message }); - } }; -exports.viewPath = (req, res) => { - debug('View path %s', req.params.path); - - resolve_public_link(req.params.id, (ident) => { - let id = null; - if (ident.is_link) { - id = ident.link; - } else { - id = ident.compendium; - } - - let size = req.query.size || null; - - Compendium.findOne({ id: ident.compendium }).select('id').lean().exec((err, compendium) => { - if (err || compendium == null) { - res.status(404).send({ error: 'no compendium with this id' }); - } else { - let localPath = path.join(config.fs.compendium, ident.compendium, req.params.path); - try { - innerSend = function(response, filePath) { - mimetype = mime.lookup(filePath) || rewriteTree.extraMimeTypes(path.extname(filePath)); - response.type(mimetype).sendFile(filePath, {}, (err) => { - if (err) { - debug("Error viewing path: %o", err) - } else { - debug('Returned %s for %s as %s', filePath, req.params.path, mimetype); - } - }); - } - - debug('Accessing %s', localPath); - fs.accessSync(localPath); //throws if does not exist - if (size) { - resize(localPath, size, (finalPath, err, code) => { - if (err) { - let status = code || 500; - res.status(status).send({ error: err }); - return; - } +exports.viewData = (req, res) => { + debug('[%s] View data', req.params.id); - innerSend(res, finalPath); - }); - } else { - innerSend(res, localPath); - } - } catch (e) { - debug('Error accessing path: %s', e); - res.status(500).send({ error: e.message }); - return; + resolve_public_link(req.params.id, (ident) => { + let id = null; + if (ident.is_link) { + id = ident.link; + } else { + id = ident.compendium; } - } + + Compendium.findOne({id: ident.compendium}).select('id').lean().exec((err, compendium) => { + if (err || compendium == null) { + res.status(404).send({error: 'no compendium with this id'}); + } else { + let localPath = path.join(config.fs.compendium, ident.compendium); + try { + debug('Reading file listing from %s', localPath); + fs.accessSync(localPath); //throws if does not exist + + let answer = rewriteTree.rewriteTree(dirTree(localPath), + localPath.length, // remove local fs path + '/api/v1/compendium/' + id + '/data' // prepend proper location + ); + answer.name = id; + + res.status(200).send(answer); + } catch (e) { + debug('Error reading file listing: %s', e); + res.status(500).send({error: e.message}); + return; + } + } + }); }); - }); }; -exports.viewData = (req, res) => { - debug('[%s] View data', req.params.id); - - resolve_public_link(req.params.id, (ident) => { - let id = null; - if (ident.is_link) { - id = ident.link; - } else { - id = ident.compendium; +exports.addCompendiumToJournal = (req, res) => { + if (!req.params.id) { + res.status('400').send('No compendium id provided!'); + return; + } + + if (!req.body.hasOwnProperty('journal')) { + res.status('400').send('No journal id provided!'); + return; } - - Compendium.findOne({ id: ident.compendium }).select('id').lean().exec((err, compendium) => { - if (err || compendium == null) { - res.status(404).send({ error: 'no compendium with this id' }); - } else { - let localPath = path.join(config.fs.compendium, ident.compendium); - try { - debug('Reading file listing from %s', localPath); - fs.accessSync(localPath); //throws if does not exist - - let answer = rewriteTree.rewriteTree(dirTree(localPath), - localPath.length, // remove local fs path - '/api/v1/compendium/' + id + '/data' // prepend proper location - ); - answer.name = id; - - res.status(200).send(answer); - } catch (e) { - debug('Error reading file listing: %s', e); - res.status(500).send({ error: e.message }); - return; + + debug('[%s] Push compendium to journal %s as candidate', req.params.id, req.body.journal); + + resolve_public_link(req.params.id, (ident) => { + let id; + if (ident.is_link) { + id = ident.link; + debug('[%s] Compendium is public link', id); + } else { + id = ident.compendium; } - } + + Compendium.findOne({id: id}, (err, compendium) => { + if (err || !compendium) { + debug('[%s] No compendium with this ID found!', id); + res.status('404').send(); + return; + } + + if (req.user.orcid !== compendium.user) { + debug('[%s] User %s is not owner of compendium', id, req.user.orcid); + res.status('403').send(); + return; + } + + Journal.findOne({id: req.body.journal}, (err, journal) => { + if (err || !journal) { + debug('[%s] Could not find Journal with ID: %s', id, req.body.journal); + res.status('404').send(); + return; + } + + journal.compendiaCandidates.push(id); + + journal.save(err => { + if (err) { + debug('[%s] Could not update journal %s in database', id, journal.id); + res.status('500').send('Could not save to database'); + return; + } + + debug('[%s] Successfully added compendium as candidate to journal %s', id, journal.id); + + res.status('200').send(); + }); + }); + }); }); - }); -}; +} diff --git a/controllers/dns.js b/controllers/dns.js new file mode 100644 index 0000000..5b771f5 --- /dev/null +++ b/controllers/dns.js @@ -0,0 +1,65 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +let Dns = require('../lib/model/dns'); +let dnsManager = require('../lib/dns-manager'); +const debug = require('debug')('muncher:dns'); + +exports.startServersOnStartup = function () { + return new Promise((fulfill, reject) => { + debug("Starting DNS servers on startup"); + let promiseArray = []; + Dns.find((err, docs) => { + if (err) { + debug("Error searching for DNS Server configurations: %O", err); + reject(err); + } else if (docs.length < 1) { + debug("No DNS Server configurations in database"); + reject("No DNS configurations in database"); + } else { + for (let dns of docs) { + promiseArray.push(new Promise((fulfill2, reject2) => { + debug("Starting DNS Server: %s", dns.id); + dnsManager + .stopAndRemoveDnsServer(dns.id) + .then(() => { + buildNewDnsServer(dns.id) + .then(dnsManager.startNewDnsServer) + .then(() => { + debug("Successfully started DNS Server: %s", dns.id); + fulfill2(); + }) + .catch((err) => { + debug("Error starting DNS Server: %O", err); + reject2(err); + }); + }); + })); + } + } + Promise.all(promiseArray) + .then(() => { + debug("Successfully started all DNS Servers"); + fulfill(); + }) + .catch((error) => { + debug("Error starting DNS Servers: %O", error); + reject(error); + }); + }); + }); +} diff --git a/controllers/job.js b/controllers/job.js index 95a5bd8..a98ddf4 100644 --- a/controllers/job.js +++ b/controllers/job.js @@ -37,7 +37,7 @@ const PublicLink = require('../lib/model/link'); const alwaysStepFields = ["start", "end", "status"]; const allStepsValue = "all"; -exports.listJobs = (req, res) => { +exports.listJobs = (req, res) => { var answer = {}; var filter = {}; var limit = parseInt(req.query.limit || config.list_limit, 10); @@ -103,7 +103,7 @@ exports.listJobs = (req, res) => { let link_ids = links.map((link) => { return link.id; }); - + if (requestedFields.length < 1) { answer.results = jobs.map((job) => { if (link_ids.indexOf(job.compendium_id) < 0 || job.compendium_id === req.query.compendium_id) @@ -113,19 +113,19 @@ exports.listJobs = (req, res) => { }); } else { answer.results = jobs.map((job) => { - if (link_ids.indexOf(job.compendium_id) < 0) + if (link_ids.indexOf(job.compendium_id) < 0) jobItem = { id: job.id }; requestedFields.forEach((elem) => { jobItem[elem] = job[elem]; }); - + return jobItem; }).filter(elem => { return elem != null; }); } - - done(answer); + + done(answer); } }); } @@ -252,7 +252,7 @@ exports.createJob = (req, res) => { req.user = { orcid: 'link.' + ident.link }; } else { id = ident.compendium; - + // check user level if (!ident.is_link && !req.isAuthenticated()) { res.status(401).send({ error: 'user is not authenticated' }); @@ -265,7 +265,7 @@ exports.createJob = (req, res) => { } // check compendium existence and load its metadata - Compendium.findOne({ id: ident.compendium }).select('id user candidate metadata bag compendium').exec((err, compendium) => { + Compendium.findOne({ id: ident.compendium }).select('id user journal candidate metadata bag compendium').exec((err, compendium) => { // eslint-disable-next-line no-eq-null, eqeqeq if (err || compendium == null) { debug('[%s] compendium not found, cannot create job: %o', job_id, ident); @@ -282,6 +282,7 @@ exports.createJob = (req, res) => { var executionJob = new Job({ id: job_id, user: req.user.orcid, + journal: compendium.journal, compendium_id: id }); diff --git a/controllers/journal.js b/controllers/journal.js new file mode 100644 index 0000000..cfe5751 --- /dev/null +++ b/controllers/journal.js @@ -0,0 +1,629 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const config = require('../config/config'); +const debug = require('debug')('muncher:journal'); +const randomstring = require('randomstring'); +const dnsBuilder = require('../lib/dns-manager'); +const domain = require('../lib/domain'); +const publisher = require('../lib/publisher'); +const resolve_public_link = require('./link').resolve_public_link; + +let Journal = require('../lib/model/journal'); +const Compendium = require('../lib/model/compendium'); + +exports.create = (req, res) => { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + let journal_id = randomstring.generate(config.id_length); + debug('[%s] Create new journal with name: %s', journal_id, req.body.name); + + // check parameters + if (!req.body.name) { + debug('[%s] name parameter not provided', journal_id); + res.status(400).send({error: 'name required'}); + return; + } + + if (!req.body.domains) { + debug('[%s] domains parameter not provided', journal_id); + res.status(400).send({error: 'domains required'}); + return; + } + + if (!Array.isArray(req.body.domains)) { + debug('[%s] domains parameter is not an array', journal_id); + res.status(400).send({error: 'domains not array'}); + return; + } + + if (req.user.level < config.user.level.manage_journal) { + debug('[%s] User is not allowed to create a journal', journal_id); + res.status(401).send(); + return; + } + + domain.validateDomains(req.body.domains) + .then(() => { + domain.addDomainsToDb(req.body.domains) + .then((domains) => { + let newJournal = new Journal({ + id: journal_id, + name: req.body.name, + domains: domains.sort(), + owner: req.user.orcid + }); + + newJournal.save(err => { + if (err) { + debug('[%s] Error saving new journal: %O', journal_id, err); + res.status(500).send({error: 'Error saving new journal to database'}); + return; + } + debug('[%s] Successfully saved new journal', journal_id) + res.status(200).send({id: journal_id}); + dnsBuilder.addToDns(journal_id, config.dns.priority.journal); + }); + }) + .catch(err => { + debug('[%s] Error saving domains provided by journal: %O', journal_id, err); + res.status(500).send({error: 'Error saving new journal to database'}); + }); + }) + .catch(err => { + res.status(400).send({error: 'List of domains includes invalid domains: ' + err.toString()}); + }); +} + +exports.update = (req, res) => { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Update journal: No ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_journal) { + debug('[%s] User is not allowed to edit a journal', req.params.id); + res.status(401).send(); + return; + } + + let journalId = req.params.id; + + Journal.findOne({id: journalId}, (err, journal) => { + if (err) { + debug('[%s] Error finding journal: %O', journalId, err); + res.status(500).send({error: 'Error finding journal in database'}); + return; + } + if (!journal) { + debug('[%s] No journal with this id found', journalId); + res.status(500).send({error: 'No journal with this id found'}); + return; + } + + if (req.user.orcid !== journal.owner) { + res.status('403').send(); + return; + } + + if (req.body.name) { + debug('[%s] Updating journal, new Name: %s', journalId, req.body.name); + } + if (req.body.domains) { + debug('[%s] Updating journal, new domains list: %O', journalId, req.body.domains); + } + + let oldDomainList = journal.domains; + + domain.validateDomains(req.body.domains) + .then(async () => { + domain.addDomainsToDb(req.body.domains) + .then(async (domains) => { + let belongsToPublisher = await publisher.includesJournal(journalId); + + if (belongsToPublisher && !journal.domains.every(d => belongsToPublisher.domains.includes(d))) { + res.status(500).send({error: 'Publisher does not allow all domains!'}); + return; + } + + journal.name = req.body.name; + journal.domains = domains.sort(); + + journal.save(err => { + if (err) { + debug('[%s] Error updating journal: %O', journal.id, err); + res.status(500).send({error: 'Error updating journal'}); + return; + } + debug('[%s] Successfully updated journal', journal.id) + res.status(200).send(); + dnsBuilder.removeJournalFromDns(journal.id, config.dns.priority.journal); + if (req.body.domains) { + domain.maybeDelete(oldDomainList); + } + dnsBuilder.addToDns(journal.id, config.dns.priority.journal); + }); + }).catch(err => { + debug('[%s] Error saving domains provided by journal: %O', journal.id, err); + res.status(500).send({error: 'Error updating journal'}); + }); + }) + .catch(err => { + res.status(400).send({error: 'List of domains includes invalid domains: ' + err.toString()}); + }); + }); +} + +exports.addDomain = function (req, res) { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Add domain: No ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_journal) { + debug('[%s] User is not allowed to edit a journal', req.params.id); + res.status(401).send(); + return; + } + + Journal.findOne({id: req.params.id}, (err, journal) => { + if (err) { + debug('[%s] Error finding journal: %O', req.params.id, err); + res.status(500).send({error: 'Error finding journal in database'}); + return; + } + if (!journal) { + debug('[%s] No journal with this id found', req.params.id); + res.status(500).send({error: 'No journal with this id found'}); + return; + } + + if (req.user.orcid !== journal.owner) { + res.status('403').send(); + return; + } + + let domainArray = []; + domainArray.push(req.body.url); + + domain.validateDomains(domainArray) + .then(() => { + domain.addDomainsToDb(domainArray) + .then(async (domains) => { + let belongsToPublisher = await publisher.includesJournal(journal.id); + + if (belongsToPublisher && !journal.domains.every(d => belongsToPublisher.domains.includes(d))) { + res.status(500).send({error: 'Publisher does not allow all domains!'}); + return; + } + + if (journal.domains.includes(domains[0])) { + res.status(400).send({error: 'domain is already in the list'}); + return; + } + + journal.domains.push(domains[0]); + + journal.domains = journal.domains.sort(); + + journal.save(err => { + if (err) { + debug('[%s] Error updating journal: %O', journal.id, err); + res.status(500).send({error: 'Error updating journal'}); + return; + } + debug('[%s] Successfully updated journal', journal.id) + res.status(200).send(); + dnsBuilder.removeJournalFromDns(journal.id, config.dns.priority.journal); + dnsBuilder.addToDns(journal.id, config.dns.priority.journal); + }); + }).catch(err => { + debug('[%s] Error saving domains provided by journal: %O', journal.id, err); + res.status(500).send({error: 'Error updating journal'}); + }); + }) + .catch(err => { + debug('[%s] Domain is not a valid domain: %O', journal.id, err); + res.status(400).send({error: 'Domain is not a valid domain: ' + err.toString()}); + }); + }); +} + +exports.removeDomain = function (req, res) { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Remove Domain from journal: No ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_journal) { + debug('[%s] User is not allowed to edit a journal', req.params.id); + res.status(401).send(); + return; + } + + Journal.findOne({id: req.params.id}, (err, journal) => { + if (err) { + debug('[%s] Error finding journal: %O', req.params.id, err); + res.status(500).send({error: 'Error finding journal in database'}); + return; + } + if (!journal) { + debug('[%s] No journal with this id found', req.params.id); + res.status(500).send({error: 'No journal with this id found'}); + return; + } + + if (req.user.orcid !== journal.owner) { + res.status('403').send(); + return; + } + + + let domainArray = []; + domainArray.push(req.body.url); + + domain.validateDomains(domainArray) + .then(() => { + domain.addDomainsToDb(domainArray) + .then((domains) => { + if (!journal.domains.includes(domains[0])) { + res.status(400).send({error: 'Domain is not in the list'}); + return; + } + + journal.domains.splice(journal.domains.indexOf(domains[0]), 1).sort(); + + journal.save(err => { + if (err) { + debug('[%s] Error updating journal: %O', journal.id, err); + res.status(500).send({error: 'Error updating journal'}); + return; + } + debug('[%s] Successfully updated journal', journal.id) + res.status(200).send(); + dnsBuilder.removeJournalFromDns(journal.id, config.dns.priority.journal); + if (req.body.domains) { + domain.maybeDelete(domains); + } + dnsBuilder.addToDns(journal.id, config.dns.priority.journal); + }); + }).catch(err => { + debug('[%s] Error saving domains provided by journal: %O', journal.id, err); + res.status(500).send({error: 'Error updating journal'}); + }); + }) + .catch(err => { + res.status(400).send({error: 'List of domains includes invalid domains: ' + err.toString()}); + }); + }); +} + +exports.addToPublisher = function (req, res) { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Add Journal to Publisher: No ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_journal) { + debug('[%s] User is not allowed to edit a journal', req.params.id); + res.status(401).send(); + return; + } + + Journal.findOne({id: req.params.id}, (err, journal) => { + if (err) { + res.status('500').send(); + return; + } + if (!journal) { + res.status('404').send(); + return; + } + if (req.user.orcid !== journal.owner) { + res.status('403').send(); + return; + } + publisher.addJournal(req.body.publisher, journal) + .then(() => { + debug('[%s] Successfully added journal as candidate to publisher %s', journal.id, req.body.publisher); + res.status(200).send(); + }) + .catch(err => { + debug('[%s] Error adding journal as candidate to publisher %s: %O', journal.id, req.body.publisher, err); + res.status(400).send({error: err}); + }); + }); +} + +exports.listJournal = function (req, res) { + debug('Get list of journals'); + Journal.find({}, '-_id id name domains compendia', (err, journals) => { + if (err) { + debug('Error getting list of journals from database: %O', err); + res.status(500).send("Error getting list of journals from database"); + return; + } + + res.status(200).send(journals); + }) +} + +exports.viewJournal = function (req, res) { + if (!req.isAuthenticated()) { + req.status('401').send(); + return; + } + + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + let journalId = req.params.id; + + debug('[%s] Getting journal', journalId) + + Journal.findOne({id: journalId}, '-_id id name domains compendia', (err, journal) => { + if (err) { + debug('[%s] Error getting Journal from database: %O', journalId, err); + res.status('500').send("Error getting Journal from database"); + return; + } else if (!journal) { + debug('[%s] No Journal with this ID found', journalId); + res.status('404').send(); + return; + } + + res.status('200').send(journal); + }); +} + +exports.getJournal = function (req, res) { + if (!req.isAuthenticated()) { + req.status('401').send(); + return; + } + + if (!req.user || req.user.level === config.user.level.manage_journal) { + req.status('403').send(); + return; + } + + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + let journalId = req.params.id; + + debug('[%s] Getting Journal', journalId) + + Journal.findOne({id: journalId}, (err, journal) => { + if (err) { + debug('[%s] Error getting Journal from database: %O', journalId, err); + res.status('500').send("Error getting Journal from database"); + return; + } + if (!journal) { + debug('[%s] No Journal with this ID found', journalId); + res.status('404').send(); + return; + } + if (req.user.orcid !== journal.owner) { + res.status('403').send(); + return; + } + + res.status('200').send(journal); + }); +} + +exports.getJournalDomains = function (req, res) { + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + let journalId = req.params.id; + + debug('[%s] Getting Journal Domains', journalId) + + Journal.findOne({id: journalId}, (err, journal) => { + if (err) { + debug('[%s] Error getting Journal from database: %O', journalId, err); + res.status('500').send("Error getting Journal from database"); + return; + } + if (!journal) { + debug('[%s] No Journal with this ID found', journalId); + res.status('404').send(); + return; + } + domain.getDomainsForJournal(journal) + .then(domains => { + res.status('200').send(domains); + }) + .catch(err => { + res.status('500').send(); + }); + }); +} + +exports.getPossibleJournalsFromDomainList = function (req, res) { + if (!req.body.domains) { + debug('domains parameter not provided'); + res.status(400).send({error: 'domains required'}); + return; + } + + if (!Array.isArray(req.body.domains)) { + debug('domains parameter is not an array'); + res.status(400).send({error: 'domains not array'}); + return; + } + + debug("Asking for possible Journals based on domain list: %O", req.body.domains); + + domain.parseDomains(req.body.domains) + .then(domains => { + let promiseArray = []; + let domainIds = []; + for (let dom of domains) { + promiseArray.push(new Promise(async (fulfill, reject) => { + domain.checkExistence(dom) + .then(resDomain => { + domainIds.push(resDomain.id); + fulfill(); + }) + .catch(domain => { + reject(domain); + }); + })); + } + + Promise.all(promiseArray) + .then(() => { + Journal.find({domains: {$all: domainIds}}, (err, journals) => { + if (err || !journals || !Array.isArray(journals) || journals.length < 1) { + debug("Found no journal for queried domain list"); + res.status('404').send("No journal found for the queried domains"); + } else { + debug("Found journals for queried domain list: %O", journals); + res.status('200').send(journals); + } + }) + }) + .catch(err => { + debug("Found no journal for queried domain list"); + res.status('404').send("No journal found for the queried domains"); + }); + }); +} + +exports.acceptCompendium = function (req, res) { + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + if (!req.body.hasOwnProperty('compendium')) { + res.status('400').send("No ID provided!"); + return; + } + + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (req.user.level < config.user.level.manage_journal) { + res.status(401).send(); + return; + } + + let journalId = req.params.id; + let compendiumId = req.body.compendium; + + debug("[%s] Accept compendium %s", journalId, compendiumId); + + resolve_public_link(compendiumId, (ident) => { + if (ident.is_link) { + compendiumId = ident.link; + debug('[%s] Compendium is public link', compendiumId); + } else { + compendiumId = ident.compendium; + } + + Journal.findOne({id: journalId}, (err, journal) => { + if (err || !journal) { + debug("[%s] No journal found", journalId); + res.status('404').send(); + return; + } + + if (journal.owner !== req.user.orcid) { + debug("[%s] User is not owner of this journal", journalId); + res.status('403').send(); + return; + } + + Compendium.findOne({id: compendiumId}, (err, compendium) => { + if (err || !compendium) { + debug("[%s] No compendium found with id %s", journalId, compendiumId); + res.status('404').send(); + return; + } + + if (!journal.compendiaCandidates.includes(compendiumId)) { + debug("[%s] Compendium %s is not candidate for this journal", journalId, compendiumId); + res.status('400').send(); + return; + } + + let index = journal.compendiaCandidates.indexOf(compendiumId); + journal.compendiaCandidates.splice(index, 1); + compendium.journal = journal.id; + journal.compendia.push(compendiumId) + + journal.save(err => { + if (err) { + debug('[%s] Error saving journal: %O', journalId, err); + res.status(500).send({error: 'Error saving journal to database'}); + return; + } + debug('[%s] Successfully saved journal', journalId); + compendium.save(err => { + if (err) { + debug('[%s] Error saving compendium: %O', journalId, err); + res.status(500).send({error: 'Error saving compendium to database'}); + return; + } + debug('[%s] Successfully accepted compendium %s at this journal', journalId, compendiumId); + res.status(200).send(); + }); + }); + }); + }); + }); +} diff --git a/controllers/publisher.js b/controllers/publisher.js new file mode 100644 index 0000000..c3eec12 --- /dev/null +++ b/controllers/publisher.js @@ -0,0 +1,673 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const config = require('../config/config'); +const debug = require('debug')('muncher:publisher'); +const randomstring = require('randomstring'); +const dnsBuilder = require('../lib/dns-manager'); +const domain = require('../lib/domain'); +const journal = require('../lib/journal') + +let Publisher = require('../lib/model/publisher'); + +exports.create = (req, res) => { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + let publisher_id = randomstring.generate(config.id_length); + debug('[%s] Create new publisher with name: %s', publisher_id, req.body.name); + + // check parameters + if (!req.body.name) { + debug('[%s] name parameter not provided', publisher_id); + res.status(400).send({error: 'name required'}); + return; + } + + if (!req.body.domains) { + debug('[%s] domains parameter not provided', publisher_id); + res.status(400).send({error: 'domains required'}); + return; + } + + if (!Array.isArray(req.body.domains)) { + debug('[%s] domains parameter is not an array', publisher_id); + res.status(400).send({error: 'domains not array'}); + return; + } + + if (req.user.level < config.user.level.manage_publisher) { + debug('[%s] User is not allowed to create a publisher', publisher_id); + res.status(401).send(); + return; + } + + domain.validateDomains(req.body.domains) + .then(() => { + let journals = []; + if (req.body.journals) + journals = req.body.journals; + + journal.validateJournals(journals) + .then(() => { + domain.addDomainsToDb(req.body.domains) + .then((domains) => { + let newPublisher = new Publisher({ + id: publisher_id, + name: req.body.name, + domains: domains.sort(), + journals: journals, + owner: req.user.orcid, + journalCandidates: [] + }); + + newPublisher.save(err => { + if (err) { + debug('[%s] Error saving new publisher: %O', publisher_id, err); + res.status(500).send({error: 'Error saving new publisher to database'}); + return; + } + debug('[%s] Successfully saved new publisher', publisher_id) + res.status(200).send({id: publisher_id}); + dnsBuilder.addToDns(publisher_id, config.dns.priority.publisher); + }); + }) + .catch(err => { + debug('[%s] Error saving domains provided by publisher: %O', publisher_id, err); + res.status(500).send({error: 'Error saving new publisher to database'}); + }); + }) + .catch(err => { + res.status(400).send({error: 'List of journals includes invalid journals: ' + err.toString()}); + }) + }) + .catch(err => { + res.status(400).send({error: 'List of domains includes invalid domains: ' + err.toString()}); + }); +} + +exports.update = (req, res) => { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Update publisher: No ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_publisher) { + debug('[%s] User is not allowed to edit a publisher', req.params.id); + res.status(401).send(); + return; + } + + let publisherId = req.params.id; + + Publisher.findOne({id: publisherId}, (err, publisher) => { + if (err) { + debug('[%s] Error finding publisher: %O', publisherId, err); + res.status(500).send({error: 'Error finding publisher in database'}); + return; + } + if (!publisher) { + debug('[%s] No publisher with this id found', publisherId); + res.status(500).send({error: 'No publisher with this id found'}); + return; + } + if (req.user.orcid !== publisher.owner) { + res.status('403').send(); + return; + } + if (req.body.name) { + debug('[%s] Updating publisher, new Name: %s', publisherId, req.body.name); + } + if (req.body.domains) { + debug('[%s] Updating publisher, new domain list: %O', publisherId, req.body.domains); + } + + let oldDomainList = publisher.domains; + + domain.validateDomains(req.body.domains) + .then(() => { + let journals = []; + if (req.body.journals) + journals = req.body.journals; + + journal.validateJournals(journals) + .then(() => { + domain.addDomainsToDb(req.body.domains) + .then((domains) => { + publisher.name = req.body.name; + publisher.domains = domains.sort(); + publisher.journals = journals; + + publisher.save(err => { + if (err) { + debug('[%s] Error updating publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + return; + } + debug('[%s] Successfully updated publisher', publisher.id) + res.status(200).send(); + dnsBuilder.removeJournalFromDns(publisher.id, config.dns.priority.publisher); + if (req.body.domains) { + domain.maybeDelete(oldDomainList); + } + dnsBuilder.addToDns(publisher.id, config.dns.priority.publisher); + }); + }).catch(err => { + debug('[%s] Error saving domains provided by publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + }); + }) + .catch(err => { + res.status(400).send({error: 'List of journals includes invalid journals: ' + err.toString()}); + }) + }) + .catch(err => { + res.status(400).send({error: 'List of domains includes invalid domains: ' + err.toString()}); + }); + }); +} + +exports.addDomain = function (req, res) { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Add domain: No ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_publisher) { + debug('[%s] User is not allowed to edit a publisher', req.params.id); + res.status(401).send(); + return; + } + + Publisher.findOne({id: req.params.id}, (err, publisher) => { + if (err) { + debug('[%s] Error finding publisher: %O', req.params.id, err); + res.status(500).send({error: 'Error finding publisher in database'}); + return; + } + if (!publisher) { + debug('[%s] No publisher with this id found', req.params.id); + res.status(500).send({error: 'No publisher with this id found'}); + return; + } + if (req.user.orcid !== publisher.owner) { + res.status('403').send(); + return; + } + + let domainArray = []; + domainArray.push(req.body.url); + + domain.validateDomains(domainArray) + .then(() => { + domain.addDomainsToDb(domainArray) + .then((domains) => { + if (publisher.domains.includes(domains[0])) { + res.status(400).send({error: 'Domain is already in the list'}); + return; + } + + publisher.domains.push(domains[0]); + + publisher.domains = publisher.domains.sort(); + + publisher.save(err => { + if (err) { + debug('[%s] Error updating publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + return; + } + debug('[%s] Successfully updated publisher', publisher.id) + res.status(200).send(); + dnsBuilder.removeJournalFromDns(publisher.id, config.dns.priority.publisher); + dnsBuilder.addToDns(publisher.id, config.dns.priority.publisher); + }); + }).catch(err => { + debug('[%s] Error saving domains provided by publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + }); + }) + .catch(err => { + debug('[%s] Domain is not a valid domain: %O', publisher.id, err); + res.status(400).send({error: 'Domain is not a valid domain: ' + err.toString()}); + }); + }); +} + +exports.removeDomain = function (req, res) { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Remove Domain from publisher: No ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_publisher) { + debug('[%s] User is not allowed to edit a publisher', req.params.id); + res.status(401).send(); + return; + } + + Publisher.findOne({id: req.params.id}, (err, publisher) => { + if (err) { + debug('[%s] Error finding publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error finding publisher in database'}); + return; + } + if (!publisher) { + debug('[%s] No publisher with this id found', publisher.id); + res.status(500).send({error: 'No publisher with this id found'}); + return; + } + if (req.user.orcid !== publisher.owner) { + res.status('403').send(); + return; + } + + let domainArray = []; + domainArray.push(req.body.url); + + domain.validateDomains(domainArray) + .then(() => { + domain.addDomainsToDb(domainArray) + .then((domains) => { + if (!publisher.domains.includes(domains[0])) { + res.status(400).send({error: 'Domain is not in the list'}); + return; + } + + publisher.domains.splice(publisher.domains.indexOf(domains[0]), 1).sort(); + + publisher.save(err => { + if (err) { + debug('[%s] Error updating publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + return; + } + debug('[%s] Successfully updated publisher', publisher.id) + res.status(200).send(); + dnsBuilder.removeJournalFromDns(publisher.id, config.dns.priority.publisher); + if (req.body.domains) { + domain.maybeDelete(domains); + } + dnsBuilder.addToDns(publisher.id, config.dns.priority.publisher); + }); + }).catch(err => { + debug('[%s] Error saving domains provided by publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + }); + }) + .catch(err => { + res.status(400).send({error: 'List of domains includes invalid domains: ' + err.toString()}); + }); + }); +} + +exports.addJournal = function (req, res) { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Add journal to publisher: No publisher ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (!req.body.journalId) { + debug('Add journal to publisher: No journal ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_publisher) { + debug('[%s] User is not allowed to edit a publisher', req.params.id); + res.status(401).send(); + return; + } + + let publisherId = req.params.id; + let journalId = req.body.journalId; + + Publisher.findOne({id: publisherId}, (err, publisher) => { + if (err) { + debug('[%s] Error finding publisher: %O', publisherId, err); + res.status(500).send({error: 'Error finding publisher in database'}); + return; + } + if (!publisher) { + debug('[%s] No publisher with this id found', publisherId); + res.status(500).send({error: 'No publisher with this id found'}); + return; + } + if (req.user.orcid !== publisher.owner) { + res.status('403').send(); + return; + } + + let journals = []; + journals.push(journalId); + + journal.validateJournals(journals) + .then(() => { + publisher.journals.push(journalId); + publisher.save(err => { + if (err) { + debug('[%s] Error updating publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + return; + } + debug('[%s] Successfully updated publisher', publisher.id) + res.status(200).send(); + }); + }) + .catch(err => { + res.status(400).send({error: 'Journal not found: ' + err.toString()}); + }) + }) +} + +exports.confirmJournal = function (req, res) { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Confirm journal: No publisher ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (!req.body.journalId) { + debug('Confirm journal: No journal ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_publisher) { + debug('[%s] User is not allowed to edit a publisher', req.params.id); + res.status(401).send(); + return; + } + + let publisherId = req.params.id; + let journalId = req.body.journalId; + + Publisher.findOne({id: publisherId}, (err, publisher) => { + if (err || !publisher) { + debug('[%s] No publisher with this ID', publisherId); + res.status(404).send(); + return; + } + if (req.user.orcid !== publisher.owner) { + res.status('403').send(); + return; + } + + journal.validateJournals([journalId]) + .then(() => { + if (publisher.journalCandidates.includes(journalId)) { + publisher.journalCandidates = publisher.journalCandidates.filter(el => el !== journalId); + publisher.journals.push(journalId); + + publisher.save(err => { + if (err) { + debug('[%s] Error updating publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + return; + } + debug('[%s] Successfully updated publisher', publisher.id) + res.status(200).send(); + }); + } else { + debug('[%s] Journal is no candidate for this publisher', publisherId); + res.status(500).send(); + } + }) + .catch(err => { + res.status(400).send({error: 'Journal not found: ' + err.toString()}); + }); + }) +} + +exports.removeJournal = function (req, res) { + if (!req.isAuthenticated()) { + res.status('401').send(); + return; + } + + if (!req.params.id) { + debug('Remove journal: No publisher ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (!req.body.journalId) { + debug('Remove journal: No journal ID provided'); + res.status(400).send({error: 'No ID provided'}); + return; + } + + if (req.user.level < config.user.level.manage_publisher) { + debug('[%s] User is not allowed to edit a publisher', req.params.id); + res.status(401).send(); + return; + } + + let publisherId = req.params.id; + let journalId = req.body.journalId; + + Publisher.findOne({id: publisherId}, (err, publisher) => { + if (err || !publisher) { + debug('[%s] No publisher with this ID', publisherId); + res.status(404).send(); + return; + } + if (req.user.orcid !== publisher.owner) { + res.status('403').send(); + return; + } + + journal.validateJournals([journalId]) + .then(() => { + if (publisher.journals.includes(journalId)) { + publisher.journals = publisher.journals.filter(el => el !== journalId); + + publisher.save(err => { + if (err) { + debug('[%s] Error updating publisher: %O', publisher.id, err); + res.status(500).send({error: 'Error updating publisher'}); + return; + } + debug('[%s] Successfully updated publisher', publisher.id) + res.status(200).send(); + }); + } else { + debug('[%s] Journal does not belong to this publisher', publisherId); + res.status(500).send(); + } + }) + .catch(err => { + res.status(400).send({error: 'Journal not found: ' + err.toString()}); + }); + }) +} + +exports.listPublishers = function (req, res) { + debug('Get list of publishers'); + Publisher.find({}, '-_id id name domains journals', (err, publishers) => { + if (err) { + debug('Error getting list of publishers from database: %O', err); + res.status(500).send("Error getting list of publishers from database"); + return; + } + + res.status(200).send(publishers); + }) +} + +exports.viewPublisher = function (req, res) { + if (!req.isAuthenticated()) { + req.status('401').send(); + return; + } + + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + let publisherId = req.params.id; + + debug('[%s] Getting publisher', publisherId) + + Publisher.findOne({id: publisherId}, '-_id id name domains journals', (err, publisher) => { + if (err) { + debug('[%s] Error getting Publisher from database: %O', publisherId, err); + res.status('500').send("Error getting Publisher from database"); + return; + } else if (!publisher) { + debug('[%s] No Publisher with this ID found', publisherId); + res.status('404').send(); + return; + } + + res.status('200').send(publisher); + }); +} + +exports.getPublisher = function (req, res) { + if (!req.isAuthenticated()) { + req.status('401').send(); + return; + } + + if (!req.user || req.user.level < config.user.level.manage_publisher) { + res.status('403').send(); + return; + } + + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + let publisherId = req.params.id; + + debug('[%s] Getting publisher', publisherId) + + Publisher.findOne({id: publisherId}, (err, publisher) => { + if (err) { + debug('[%s] Error getting Publisher from database: %O', publisherId, err); + res.status('500').send("Error getting Publisher from database"); + return; + } + if (!publisher) { + debug('[%s] No Publisher with this ID found', publisherId); + res.status('404').send(); + return; + } + if (req.user.orcid !== publisher.owner) { + res.status('403').send(); + return; + } + + res.status('200').send(publisher); + }); +} + +exports.getPublisherDomains = function (req, res) { + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + let publisherId = req.params.id; + + debug('[%s] Getting Publisher Domains', publisherId) + + Publisher.findOne({id: publisherId}, (err, publisher) => { + if (err) { + debug('[%s] Error getting Publisher from database: %O', publisherId, err); + res.status('500').send("Error getting Publisher from database"); + return; + } + if (!publisher) { + debug('[%s] No Publisher with this ID found', publisherId); + res.status('404').send(); + return; + } + domain.getDomainsForPublisher(publisher) + .then(domains => { + res.status('200').send(domains); + }) + .catch(err => { + res.status('500').send(); + }); + }); +} + +exports.getPublisherJournals = function (req, res) { + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + let publisherId = req.params.id; + + debug('[%s] Getting Publisher Journals', publisherId) + + Publisher.findOne({id: publisherId}, (err, publisher) => { + if (err) { + debug('[%s] Error getting Publisher from database: %O', publisherId, err); + res.status('500').send("Error getting Publisher from database"); + return; + } + if (!publisher) { + debug('[%s] No Publisher with this ID found', publisherId); + res.status('404').send(); + return; + } + journal.getJournalsForPublisher(publisher) + .then(domains => { + res.status('200').send(domains); + }) + .catch(err => { + res.status('500').send(); + }); + }); +} diff --git a/controllers/repository.js b/controllers/repository.js new file mode 100644 index 0000000..7b6f0ee --- /dev/null +++ b/controllers/repository.js @@ -0,0 +1,109 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const debug = require('debug')('muncher:repository'); +const https = require('https'); +const xmlParser = require('fast-xml-parser'); + +exports.listRepositories = function (req, res) { + let url = 'https://www.re3data.org/api/beta/repositories'; + if (req.query && Object.keys(req.query).length > 0) { + debug('List repositories: Making GET request to re3data with query: %O', req.query); + url += '?'; + for (let key in req.query) { + if (Array.isArray(req.query[key])) { + for (let value of req.query[key]) { + url = addToQuery(url, key, value); + } + } else { + url = addToQuery(url, key, req.query[key]); + } + } + } else + debug('List repositories: Making GET request to re3data'); + + let url2 = new URL(url); + + let re3dataReq = https.get(url2.href, result => { + debug('Received headers from re3data: %O', result.headers); + debug('Received status code from re3data.org: %d', result.statusCode); + + let receivedData = ''; + result.on('data', data => { + receivedData += data + }); + + result.on('end', () => { + debug('Successfully received repository list from re3data'); + let result = xmlParser.parse(receivedData); + if (result.hasOwnProperty('error')) { + let errorJson = xmlParser.parse(result, {ignoreAttributes: false}) + res.status(errorJson['error']['@_code']).send(errorJson['error']['@_message']); + } else { + res.status('200').send(result); + } + }); + }); + + re3dataReq.on('error', e => { + res.status('500').send(); + debug('ERROR during GET request to re3data: %O', e); + }); +} + +exports.getRepository = function (req, res) { + if (!req.params.id) { + res.status('400').send("No ID provided!"); + return; + } + + let repoXmlReq = https.get('https://www.re3data.org/api/beta/repository/' + req.params.id, result => { + let receivedXml = ''; + + result.on('data', filterData => { + receivedXml += filterData + }); + + result.on('end', async () => { + let repoJson = xmlParser.parse(receivedXml); + if (repoJson.hasOwnProperty('error')) { + let errorJson = xmlParser.parse(receivedXml, {ignoreAttributes: false}) + res.status(errorJson['error']['@_code']).send(errorJson['error']['@_message']); + } else { + res.status('200').send(repoJson); + } + }); + }); + + repoXmlReq.on('error', e => { + res.status('500').send(); + debug('ERROR during GET request to re3data: %O', e); + }); +} + +exports.getRepositoryFilter = function (req, res) { + let filter = require('../lib/re3data/re3data-metrics.json'); + res.status('200').send(filter); +} + +let addToQuery = function (url, key, value) { + if (!url.endsWith('?')) { + url += '&' + } + + return (url + key + '%5b%5d=' + value); +} diff --git a/index.js b/index.js index 2b02a7f..e03017a 100644 --- a/index.js +++ b/index.js @@ -44,7 +44,7 @@ var dbOptions = { mongoose.connection.on('error', (err) => { debug('Could not connect to MongoDB @ %s: %s'.red, dbURI, err); }); -// If the Node process ends, close the Mongoose connection +// If the Node process ends, close the Mongoose connection process.on('SIGINT', function () { mongoose.connection.close(function () { debug('Mongoose default connection disconnected through app termination signal (SIGINT)'); @@ -90,12 +90,17 @@ controllers.link = require('./controllers/link'); controllers.download = require('./controllers/download'); controllers.substitutions = require('./controllers/substitutions'); controllers.environment = require('./controllers/environment'); +controllers.dns = require('./controllers/dns'); +controllers.journal = require('./controllers/journal'); +controllers.publisher = require('./controllers/publisher'); +controllers.repository = require('./controllers/repository'); // check fs & create dirs if necessary fse.mkdirsSync(config.fs.incoming); fse.mkdirsSync(config.fs.compendium); fse.mkdirsSync(config.fs.job); fse.mkdirsSync(config.fs.cache); +fse.mkdirsSync(config.fs.dns); fse.mkdirsSync(config.fs.deleted); fse.mkdirsSync(config.payload.tarball.tmpdir); @@ -320,6 +325,7 @@ function initApp(callback) { app.get('/api/v1/compendium/:id/metadata', controllers.compendium.viewCompendiumMetadata); app.put('/api/v1/compendium/:id/metadata', upload.any(), controllers.compendium.updateCompendiumMetadata); + app.put('/api/v1/compendium/:id/journal', controllers.compendium.addCompendiumToJournal); app.get('/api/v1/job', controllers.job.listJobs); app.post('/api/v1/job', upload.any(), controllers.job.createJob); @@ -335,6 +341,35 @@ function initApp(callback) { app.get('/api/v1/environment', controllers.environment.listEnvironments); + app.post('/api/v1/publisher', controllers.publisher.create); + app.get('/api/v1/publisher', controllers.publisher.listPublishers); + app.get('/api/v1/publisher/:id', controllers.publisher.getPublisher); + app.get('/api/v1/publisher/:id/view', controllers.publisher.viewPublisher); + app.get('/api/v1/publisher/:id/domains', controllers.publisher.getPublisherDomains); + app.get('/api/v1/publisher/:id/journals', controllers.publisher.getPublisherJournals); + app.put('/api/v1/publisher/:id/update', controllers.publisher.update); + app.put('/api/v1/publisher/:id/adddomain', controllers.publisher.addDomain); + app.put('/api/v1/publisher/:id/removedomain', controllers.publisher.removeDomain); + app.put('/api/v1/publisher/:id/addjournal', controllers.publisher.addJournal); + app.put('/api/v1/publisher/:id/confirmjournal', controllers.publisher.confirmJournal); + app.put('/api/v1/publisher/:id/removejournal', controllers.publisher.removeJournal); + + app.post('/api/v1/journal', controllers.journal.create); + app.get('/api/v1/journal', controllers.journal.listJournal); + app.get('/api/v1/journal/possiblejournals', controllers.journal.getPossibleJournalsFromDomainList); + app.get('/api/v1/journal/:id', controllers.journal.getJournal); + app.get('/api/v1/journal/:id/view', controllers.journal.viewJournal); + app.get('/api/v1/journal/:id/domains', controllers.journal.getJournalDomains); + app.put('/api/v1/journal/:id/update', controllers.journal.update); + app.put('/api/v1/journal/:id/adddomain', controllers.journal.addDomain); + app.put('/api/v1/journal/:id/removedomain', controllers.journal.removeDomain); + app.put('/api/v1/journal/:id/addtopublisher', controllers.journal.addToPublisher); + app.put('/api/v1/journal/:id/acceptCompendium', controllers.journal.acceptCompendium); + + app.get('/api/v1/repository', controllers.repository.listRepositories); + app.get('/api/v1/repository/filter', controllers.repository.getRepositoryFilter); + app.get('/api/v1/repository/:id', controllers.repository.getRepository); + fulfill(); }); @@ -364,6 +399,16 @@ function initApp(callback) { } }); + startDnsServers = new Promise((fulfill, reject) => { + controllers.dns.startServersOnStartup() + .then(() => { + fulfill(); + }) + .catch((err) => { + reject(err); + }) + }); + logVersions = new Promise((fulfill, reject) => { // Python version used for bagit.py let pythonVersionCmd = 'echo $(python --version)'; @@ -396,6 +441,10 @@ function initApp(callback) { .then(logVersions) .then(configureEmailTransporter) .then(configureExpressApp) + .then(startDnsServers) + .catch((err) => { + debug('ERROR starting DNS Servers, this may result in problems later: %O', err); + }) .then(configureSlack) .then(startListening) .then(() => { diff --git a/lib/dns-manager.js b/lib/dns-manager.js new file mode 100644 index 0000000..2f6ce90 --- /dev/null +++ b/lib/dns-manager.js @@ -0,0 +1,318 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const config = require('../config/config'); +const debug = require('debug')('muncher:dns-builder'); +const Dns = require('./model/dns'); +const Journal = require('./model/journal'); +const Domain = require('./model/domain'); +const Publisher = require('./model/publisher'); +const randomstring = require('randomstring'); +const fse = require('fs-extra'); +const path = require('path'); +const Docker = require('dockerode'); +const Stream = require('stream'); +const archiver = require('archiver'); + +let docker = new Docker(); + +module.exports.addToDns = function (entityId, priority) { + let instance = null; + if (priority === config.dns.priority.journal) + instance = Journal; + else + instance = Publisher; + + instance.findOne({id: entityId}, (err, entity) => { + if (err || !entity) { + debug('[%s] Could not find owner: %O', entityId, err); + return; + } + + Dns.findOne({'domains': entity.domains, priority: priority}, (err, dns) => { + if (err) { + debug('[%s] Could not find DNS Server for owner: %O', entityId, err); + } else { + if (!dns) { + debug('[%s] No existing DNS Server for this configuration, creating new one'); + createDnsServer(entity.id, entity.domains, priority); + } else { + debug('[%s] Found DNS Server for owner: %s', entityId, dns.id); + dns.updateOne({$addToSet: {owner: entityId}}, (err, result) => { + if (err) + debug('[%s] Could not add owner to dns server configuration: %O', entityId, result); + else { + debug('[%s] Added owner to dns server configuration: %O', entityId, result); + } + }) + } + } + }); + }); +} + +module.exports.removeJournalFromDns = function (entityId, priority) { + let instance = null; + if (priority === config.dns.priority.journal) + instance = Journal; + else + instance = Publisher; + + instance.findOne({id: entityId}, (err, entity) => { + if (err || !entity) { + debug('[%s] Could not find owner: %O', entityId, err); + return; + } + + Dns.findOne({owner: entity.id}, (err, dns) => { + if (err) { + debug('[%s] Could not find DNS Server for owner: %O', entityId, err); + } else { + dns.owner.splice(dns.owner.indexOf(entity.id), 1); + if (dns.owner.length < 1) { + debug('[%s] Deleting dns server because no journals are using it!', dns.id); + Dns.deleteOne({id: dns.id}, err => { + if (err) + debug('[%s] Could not delete dns server: %O', dns.id, err); + else { + debug('[%s] Deleted dns server!', dns.id); + stopAndRemoveDnsServer(dns.id); + } + }) + } else { + dns.save(err => { + if (err) + debug('[%s] Error updating dns server configuration: %O', dns.id, err); + else { + debug('[%s] Deleted owner %s from dns server', dns.id, entityId); + } + }); + } + } + }); + }); +} + +createDnsServer = function (entityId, entityDomains, priority) { + let newId = randomstring.generate({ + length: config.id_length, + capitalization: 'lowercase' + }); + let dns = new Dns({ + id: newId, + owner: [entityId], + domains: entityDomains, + priority: priority + }); + + debug('[%s] Saving new DNS Server configuration to database: %O', newId, dns); + + dns.save((err, dns) => { + if (err) { + debug('[%s] Could not save new DNS Server configuration to database: %O', newId, err); + } else { + debug('[%s] Saved new DNS Server configuration to database', newId); + buildNewDnsServer(dns.id) + .then(() => { + startNewDnsServer(dns.id); + }); + } + }) +} + +buildNewDnsServer = function (dnsId) { + return new Promise((fulfill, reject) => { + let dnsConfigPath = path.join(config.fs.dns, dnsId); + Dns.findOne({id: dnsId}, (err, dns) => { + if (err) { + debug('[%s] Error searching in database: %O', dnsId, err); + reject(err); + } else if (!dns) { + debug('[%s] No DNS Server configuration found!', dnsId); + reject("No DNS Server configuration found!") + } else { + fse.mkdirsSync(dnsConfigPath); + fse.writeFileSync(dnsConfigPath + '/Dockerfile', config.dns.dockerfile); + fse.writeFileSync(dnsConfigPath + '/dnsmasq.conf', config.dns.dnsmasq.default); + addDomainsToDns(dnsConfigPath + '/dnsmasq.conf', dns.domains) + .then(() => { + let tarballFileName = path.join(config.payload.tarball.tmpdir, dnsId + '.tar'); + let tarballFile = fse.createWriteStream(tarballFileName); + + let archive = archiver('tar', { + gzip: config.payload.tarball.gzip, + gzipOptions: config.payload.tarball.gzipOptions, + statConcurrency: config.payload.tarball.statConcurrency + }); + + archive.on('end', function () { + debug('[%s] Packing payload to file %s completed (%s total bytes)', dnsId, tarballFileName, archive.pointer()); + + docker.buildImage(tarballFileName, {t: dns.id}, (error, output) => { + if (error) { + debug('[%s] error building image: %O', dns.id, error); + reject(error); + } else { + let lastData; + output.on('data', d => { + lastData = JSON.parse(d.toString('utf8')); + debug('[%s] [build] %o', dns.id, lastData); + }); + + output.on('end', async () => { + // check if build actually succeeded + if (lastData.error) { + debug('[%s] Docker image build FAILED: %O', dns.id, lastData); + } else if (lastData.stream && lastData.stream.startsWith('Successfully tagged')) { + debug('[%s] Created Docker image "%s", last log was "%s"', dns.id, dns.id, lastData.stream.trim()); + fse.unlink(tarballFileName, err1 => { + if (err1) + debug('[%s] Could not remove image file after build!', dns.id); + else + debug('[%s] removed image file after build!', dns.id); + + }) + fulfill(dns.id); + } + }); + } + }); + }); + + archive.pipe(tarballFile); + + archive.glob(config.payload.tarball.globPattern, { + cwd: dnsConfigPath + }) + archive.finalize(); + }); + } + }); + }); +} + +startNewDnsServer = function (dnsId) { + return new Promise((fulfill, reject) => { + Dns.findOne({id: dnsId}, (err, dns) => { + if (err) + debug('[%s] Error searching in database: %O', dnsId, err); + else if (!dns) { + debug('[%s] No DNS Server configuration found!', dnsId); + } else { + let stdStream = Stream.Writable(); + stdStream._write = function (chunk, enc, next) { + let msg = Buffer.from(chunk).toString().trim(); + debug('[%s] [run] %s', dnsId, msg); + if (msg.includes("dnsmasq: started")) { + let container = docker.getContainer(dns.id); + container.inspect(function (err, data) { + dns.ip = data.NetworkSettings.Networks.bridge.IPAddress; + dns.save(); + }); + fulfill(); + } + }; + + docker.run(dns.id, [], stdStream, {name: dns.id}, {}, function (err, data, container) { + if (err) { + debug('[%s] Error starting container: %O', dns.id, err); + reject(err); + } else { + debug('[%s] Started container: %O', dns.id, data); + debug('[%s] Started container: %O', dns.id, container.id); + } + }); + } + }); + }); +} + +stopAndRemoveDnsServer = function (dnsId) { + return new Promise((fulfill, reject) => { + docker.listContainers({ + all: true + }, (err, containers) => { + + if (containers.some(c => c.Image === dnsId)) { + console.log("stopAndRemoveDnsServer: Found container with same image"); + for (let containerInfo of containers) { + if (containerInfo.Image === dnsId) { + console.log("stopAndRemoveDnsServer: container with same image: " + containerInfo.Image); + let container = docker.getContainer(containerInfo.Id); + container.remove({force: true}, (err, data) => { + if (err) { + debug('[%s] Error removing Container: %O', dnsId, err); + reject(err); + } else + debug('[%s] Removed container!', dnsId); + let image = docker.getImage(dnsId); + image.remove({force: true}, (err, data) => { + if (err) { + debug('[%s] Error removing Image: %O', dnsId, err); + reject(err); + } else { + debug('[%s] Removed image!', dnsId); + fulfill(); + } + }); + }); + } + } + } else { + fulfill(); + } + }); + }); +} + +addDomainsToDns = function (filePath, domains) { + return new Promise(async (fulfill, reject) => { + let promiseArray = []; + for (let domain of domains) { + promiseArray.push(new Promise((fulfill2, reject2) => { + Domain.findOne({id: domain}, (err, dom) => { + if (err) { + debug('[%S] Error finding domain!', domain); + reject2(err); + } else if (!dom) { + debug('[%S] Could not find domain!', domain); + reject2("No Domain with this ID found"); + } else { + fse.appendFileSync(filePath, "server=/"); + if (dom.subdomain) { + fse.appendFileSync(filePath, dom.subdomain + "."); + } + if (dom.secondLevelDomain) { + fse.appendFileSync(filePath, dom.secondLevelDomain + "."); + } + if (dom.topLevelDomain) { + fse.appendFileSync(filePath, dom.topLevelDomain); + } + fse.appendFileSync(filePath, "/8.8.8.8\n"); + fulfill2(); + } + }) + })); + } + + fulfill(await Promise.all(promiseArray)); + }); +} + +module.exports.buildNewDnsServer = buildNewDnsServer; +module.exports.startNewDnsServer = startNewDnsServer; +module.exports.stopAndRemoveDnsServer = stopAndRemoveDnsServer; diff --git a/lib/domain.js b/lib/domain.js new file mode 100644 index 0000000..cdee5c1 --- /dev/null +++ b/lib/domain.js @@ -0,0 +1,214 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const psl = require('psl'); +const Domain = require('../lib/model/domain'); +const Journal = require('./model/journal'); +const randomstring = require('randomstring'); +const config = require('../config/config'); + +module.exports.validateDomains = function (domains) { + return new Promise(async (fulfill, reject) => { + if (domains.length < 1) { + fulfill(); + } + let notValid = []; + for (let i = 0; i < domains.length; i++) { + domains[i] = await removeProtocol(domains[i]); + if (!psl.isValid(domains[i])) { + notValid.push(domains[i]); + } + if (i === domains.length - 1) { + if (notValid.length < 1) { + fulfill(); + } else { + reject(notValid); + } + } + } + }); +} + +module.exports.addDomainsToDb = function (domains) { + return new Promise(async (fulfill, reject) => { + if (domains.length < 1) { + fulfill([]); + } + let objects = []; + let exists = []; + let parsedDomains = await parseDomains(domains); + for (let i = 0; i < parsedDomains.length; i++) { + Domain.findOne({ + topLevelDomain: parsedDomains[i].topLevelDomain, + secondLevelDomain: parsedDomains[i].secondLevelDomain, + subDomain: parsedDomains[i].subDomain + }, (err, doc) => { + if (err) + reject(err); + else { + if (!doc) { + objects.push(new Domain({ + id: randomstring.generate(config.id_length), + topLevelDomain: parsedDomains[i].topLevelDomain, + secondLevelDomain: parsedDomains[i].secondLevelDomain, + subDomain: parsedDomains[i].subDomain + })) + } else { + exists.push(doc.id); + } + } + + if (i === domains.length - 1) { + Domain.create(objects, (err1, doc) => { + if (err1) + reject(err1); + else { + fulfill(objects.map(d => d.id).concat(exists).sort()); + } + }); + } + }); + } + }); +} + +module.exports.maybeDelete = function (domains) { + for (let domain of domains) { + Journal.findOne({domains: domain.id}, (err, journal) => { + if (!journal) { + Domain.findByIdAndDelete(domain.id); + } + }); + } +} + +module.exports.getDomainsForPublisher = function (publisher) { + return new Promise((fulfill, reject) => { + getDomains(publisher) + .then(res => { + fulfill(res); + }) + .catch(err => { + reject(err); + }); + }); +} + +module.exports.getDomainsForJournal = function (journal) { + return new Promise((fulfill, reject) => { + getDomains(journal) + .then(res => { + fulfill(res); + }) + .catch(err => { + reject(err); + }); + }); +} + +module.exports.checkExistence = function (domain) { + return new Promise((fulfill, reject) => { + Domain.findOne({ + topLevelDomain: domain.topLevelDomain, + secondLevelDomain: domain.secondLevelDomain, + subDomain: domain.subDomain + }, (err, resDomain) => { + if (err || !resDomain) + reject(domain); + else { + fulfill(resDomain); + } + }) + }); +} + +getDomains = function (object) { + return new Promise((fulfill, reject) => { + let promises = []; + let domains = []; + for (let domain of object.domains) { + promises.push(new Promise((fulfill2, reject2) => { + Domain.findOne({id: domain}, '-_id id topLevelDomain secondLevelDomain subDomain', (err, domain) => { + if (err) { + reject2("Error getting domain from database") + return; + } + if (!domain) { + reject2("No domain with this ID") + return; + } + domains.push(domain); + fulfill2(); + }); + })); + } + + Promise.all(promises) + .then(() => { + fulfill(domains); + }) + .catch((error) => { + reject(error); + }); + }); +} + +removeProtocol = async function (domain) { + if (domain.includes("://")) { + let index = domain.indexOf('://'); + return domain.substring(index + 3); + } + + return domain; +} + +parseDomains = function (domains) { + return new Promise((fulfill, reject) => { + let promiseArray = []; + let parsedDomains = []; + for (let domain of domains) { + promiseArray.push(new Promise(async (fulfill2, reject2) => { + domain = await removeProtocol(domain); + let parsedDomain = psl.parse(domain); + let subdomain = ""; + if (parsedDomain.subdomain && parsedDomain.subdomain !== "www") { + if (parsedDomain.subdomain.includes("www")) { + let index = parsedDomain.subdomain.indexOf("www"); + parsedDomain.subdomain = parsedDomain.subdomain.substring(index + 4); + } + subdomain = parsedDomain.subdomain; + } + parsedDomains.push({ + topLevelDomain: parsedDomain.tld, + secondLevelDomain: parsedDomain.sld, + subDomain: subdomain + }); + fulfill2(); + })); + } + + Promise.all(promiseArray) + .then(() => { + fulfill(parsedDomains); + }) + .catch((error) => { + reject(error); + }); + }); +} + +module.exports.parseDomains = parseDomains; diff --git a/lib/executor.js b/lib/executor.js index 75321e4..2128261 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -39,6 +39,7 @@ const tarlist = require('tar').list; const config = require('../config/config'); const Job = require('../lib/model/job'); +const Dns = require('../lib/model/dns'); const checker = require('erc-checker/index').ercChecker; const manifest = require('../lib/manifest'); const saveImageFromJob = require('../lib/image').saveImageFromJob; @@ -604,7 +605,7 @@ function Executor(jobId, compendium) { return(true); } }); - + debug('[%s] Generate manifest using %s and write manifest file to %s', job_id, manifestGenerationInputFile, payloadDir); manifest.getGenerationFunction(manifestGenerationInputFile, (err0, generationFunction) => { if (err0) { @@ -860,7 +861,7 @@ function Executor(jobId, compendium) { digest = lastData.aux.ID; debug('[%s] Extracted image digest %s from log %o"', job_id, digest, lastData); } - + stepUpdate('image_build', null, msg, (err) => { if (err) { debugBuild('[%s] error appending last output log "%s" to job %s', lastData.stream, this.jobId); @@ -869,7 +870,7 @@ function Executor(jobId, compendium) { }); } catch (err) { debug('[%s] Error parsing log data: %o', this.jobId, err); - + stepUpdate('image_build', null, msg, (err) => { if (err) { debugBuild('[%s] error retrieving last output log: %s', this.jobId, err.message); @@ -1064,6 +1065,7 @@ function Executor(jobId, compendium) { } debug('[%s] Run image: %s', this.jobId, passon.imageTag); + debug('[%s] passen object: %O', this.jobId, passon); var job_id = this.jobId; var stepUpdate = this.updateStep; @@ -1128,91 +1130,113 @@ function Executor(jobId, compendium) { // remove duplicates from binds binds = Array.from(new Set(binds)); - let create_options = Object.assign( - config.bagtainer.docker.create_options, - { - name: 'muncher_job_' + this.jobId, - HostConfig: { - Binds: binds, - AutoRemove: config.bagtainer.rm - } - } - ); - let start_options = clone(config.bagtainer.docker.start_options); - debug('[%s] Starting Docker container now:\n\tcreate_options: %o\n\tstart_options: %o', - this.jobId, JSON.stringify(create_options), JSON.stringify(start_options)); - - this.updateStep('image_execute', 'running', 'Running image ' + passon.imageTag, (err) => { - if (err) { - reject(err); - return; + Dns.findOne({owner: this.compendium.journal}, (err, dnsConf) => { + let create_options; + if (err || !dnsConf) { + debug('[%s] Could not find DNS Server for this Compendium, setting NetworkDisables: true', job_id); + create_options = Object.assign( + config.bagtainer.docker.create_options, + { + name: 'muncher_job_' + this.jobId, + NetworkDisabled: true, + HostConfig: { + Binds: binds, + AutoRemove: config.bagtainer.rm + } + } + ); + } else { + debug('[%s] Found DNS Server for this Compendium, setting DNS IP: %s', job_id, dnsConf.ip); + create_options = Object.assign( + config.bagtainer.docker.create_options, + { + name: 'muncher_job_' + this.jobId, + NetworkDisabled: false, + HostConfig: { + Binds: binds, + AutoRemove: config.bagtainer.rm, + dns: [dnsConf.ip] + } + } + ); } - docker.run(passon.imageTag, [], stdStream, create_options, start_options, (err, data, container) => { - try { - passon.container = container; // pass on a reference to container for later cleanup - if (err) { - stepUpdate('image_execute', 'failure', err.toString(), (error) => { - if (error) reject(error); + let start_options = clone(config.bagtainer.docker.start_options); + debug('[%s] Starting Docker container now:\n\tcreate_options: %o\n\tstart_options: %o', + this.jobId, JSON.stringify(create_options), JSON.stringify(start_options)); - reject(new Error(err.message)); - }); - } else { - debugRun('[%s] status code: %s', this.jobId, data.StatusCode); - // check exit code of program run inside the container, see http://tldp.org/LDP/abs/html/exitcodes.html - fields = {}; - fields['steps.image_execute.statuscode'] = data.StatusCode; + this.updateStep('image_execute', 'running', 'Running image ' + passon.imageTag, (err) => { + if (err) { + reject(err); + return; + } - // save non-standard field separately - Job.updateOne({ id: job_id }, { $set: fields }, (err) => { - if (err) { - debug('[%s] Error during check step fields update: %s', job_id, err.message); - reject(err); - } else { - if (data.StatusCode === 0) { - stepUpdate('image_execute', 'success', '[finished image execution]', (error) => { - if (error) reject(error); + docker.run(passon.imageTag, [], stdStream, create_options, start_options, (err, data, container) => { + try { + passon.container = container; // pass on a reference to container for later cleanup + if (err) { + stepUpdate('image_execute', 'failure', err.toString(), (error) => { + if (error) reject(error); - fulfill(passon); - }); + reject(new Error(err.message)); + }); + } else { + debugRun('[%s] status code: %s', this.jobId, data.StatusCode); + // check exit code of program run inside the container, see http://tldp.org/LDP/abs/html/exitcodes.html + fields = {}; + fields['steps.image_execute.statuscode'] = data.StatusCode; + + // save non-standard field separately + Job.updateOne({ id: job_id }, { $set: fields }, (err) => { + if (err) { + debug('[%s] Error during check step fields update: %s', job_id, err.message); + reject(err); } else { - debugRun('[%s] ERROR: %o', job_id, data); + if (data.StatusCode === 0) { + stepUpdate('image_execute', 'success', '[finished image execution]', (error) => { + if (error) reject(error); - stepUpdate('image_execute', 'failure', '[error during image execution]', (error) => { - if (error) reject(error); - else { - container.logs({ - follow: true, - stdout: true, - stderr: true, - timestamps: true - }, function (err, stream) { - if (err) { - debugRun('[%s] Error getting container logs after non-zero status code (expected if rm is true, it is: %s): %s', - job_id, config.bagtainer.rm, err); - } else { - stream.on('data', function (data) { - debugRun('[%s] container logs ', job_id, Buffer.from(data).toString().trim()); - }); - } - }); + fulfill(passon); + }); + } else { + debugRun('[%s] ERROR: %o', job_id, data); - // do not wait for container log stream - reject(new Error('Received non-zero status code "' + data.StatusCode + '" from container')); - } - }); + stepUpdate('image_execute', 'failure', '[error during image execution]', (error) => { + if (error) reject(error); + else { + container.logs({ + follow: true, + stdout: true, + stderr: true, + timestamps: true + }, function (err, stream) { + if (err) { + debugRun('[%s] Error getting container logs after non-zero status code (expected if rm is true, it is: %s): %s', + job_id, config.bagtainer.rm, err); + } else { + stream.on('data', function (data) { + debugRun('[%s] container logs ', job_id, Buffer.from(data).toString().trim()); + }); + } + }); + + // do not wait for container log stream + reject(new Error('Received non-zero status code "' + data.StatusCode + '" from container')); + } + }); + } } - } + }); + } + } catch (e) { + this.updateStep('image_execute', 'failure', e.message, (err) => { + if (err) reject(err); + + reject(e); }); } - } catch (e) { - this.updateStep('image_execute', 'failure', e.message, (err) => { - if (err) reject(err); - - reject(e); - }); - } + }); }); }); }); @@ -1370,7 +1394,7 @@ function Executor(jobId, compendium) { if (passon.check.numTextDifferrences) { fields['steps.check.numTextDifferrences'] = passon.check.numTextDifferrences; } - + Job.updateOne({ id: job_id }, { $set: fields }, (err) => { if (err) { debug('[%s] Error during check step fields: %s', job_id, err.message); diff --git a/lib/journal.js b/lib/journal.js new file mode 100644 index 0000000..ef06b27 --- /dev/null +++ b/lib/journal.js @@ -0,0 +1,76 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const Journal = require('./model/journal'); + +module.exports.validateJournals = function (journalId) { + return new Promise((fulfill, reject) => { + if (!journalId) + reject("No IDs provided"); + + let promiseArray = []; + for (let jId of journalId) { + promiseArray.push(new Promise((fulfill2, reject2) => { + Journal.findOne({id: jId}, (err, journal) => { + if (err || !journal) + reject2("No journal with this ID") + else + fulfill2(); + }); + })); + } + + Promise.all(promiseArray) + .then(() => { + fulfill(); + }) + .catch((error) => { + reject(error); + }); + }); +} + +module.exports.getJournalsForPublisher = function (publisher) { + return new Promise((fulfill, reject) => { + let promises = []; + let journals = []; + for (let journal of publisher.journals) { + promises.push(new Promise((fulfill2, reject2) => { + Journal.findOne({id: journal}, '-_id id name domains owner compendia', (err, domain) => { + if (err) { + reject2("Error getting journal from database") + return; + } + if (!domain) { + reject2("No journal with this ID") + return; + } + journals.push(domain); + fulfill2(); + }); + })); + } + + Promise.all(promises) + .then(() => { + fulfill(journals); + }) + .catch((error) => { + reject(error); + }); + }); +} diff --git a/lib/model/compendium.js b/lib/model/compendium.js index 1efa5e4..8e24c21 100644 --- a/lib/model/compendium.js +++ b/lib/model/compendium.js @@ -20,6 +20,7 @@ var timestamps = require('mongoose-timestamp'); var Compendium = new mongoose.Schema({ id: String, user: String, + journal: String, metadata: Object, created: {type: Date, default: Date.now}, jobs: [String], diff --git a/lib/model/dns.js b/lib/model/dns.js new file mode 100644 index 0000000..08dd541 --- /dev/null +++ b/lib/model/dns.js @@ -0,0 +1,29 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +var mongoose = require('mongoose'); +var timestamps = require('mongoose-timestamp'); + +var Dns = new mongoose.Schema({ + id: String, + owner: [String], + domains: [String], + ip: String, + priority: Number +}); +Dns.plugin(timestamps); + +module.exports = mongoose.model('Dns', Dns); diff --git a/lib/model/domain.js b/lib/model/domain.js new file mode 100644 index 0000000..96b2293 --- /dev/null +++ b/lib/model/domain.js @@ -0,0 +1,28 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +var mongoose = require('mongoose'); +var timestamps = require('mongoose-timestamp'); + +var Domain = new mongoose.Schema({ + id: String, + topLevelDomain: String, + secondLevelDomain: String, + subDomain: String +}); +Domain.plugin(timestamps); + +module.exports = mongoose.model('Domain', Domain); diff --git a/lib/model/job.js b/lib/model/job.js index c7aaa12..b6ab26b 100644 --- a/lib/model/job.js +++ b/lib/model/job.js @@ -21,6 +21,7 @@ var Job = new mongoose.Schema({ id: { type: String, default: '' }, compendium_id: { type: String, default: '' }, user: { type: String, default: '' }, + journal: {type: String, default: ''}, status: { type: String, default: '' }, steps: { validate_bag: { diff --git a/lib/model/journal.js b/lib/model/journal.js new file mode 100644 index 0000000..ca6e5f2 --- /dev/null +++ b/lib/model/journal.js @@ -0,0 +1,30 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +var mongoose = require('mongoose'); +var timestamps = require('mongoose-timestamp'); + +var Journal = new mongoose.Schema({ + id: String, + name: String, + domains: [String], + owner: String, + compendia: [String], + compendiaCandidates: [String] +}); +Journal.plugin(timestamps); + +module.exports = mongoose.model('Journal', Journal); diff --git a/lib/model/publisher.js b/lib/model/publisher.js new file mode 100644 index 0000000..dd91eee --- /dev/null +++ b/lib/model/publisher.js @@ -0,0 +1,30 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +var mongoose = require('mongoose'); +var timestamps = require('mongoose-timestamp'); + +var Publisher = new mongoose.Schema({ + id: String, + name: String, + domains: [String], + journals: [String], + owner: String, + journalCandidates: [String] +}); +Publisher.plugin(timestamps); + +module.exports = mongoose.model('Publisher', Publisher); diff --git a/lib/model/user.js b/lib/model/user.js index cdd86c5..406a6bf 100644 --- a/lib/model/user.js +++ b/lib/model/user.js @@ -18,10 +18,10 @@ var mongoose = require('mongoose'); var timestamps = require('mongoose-timestamp'); var User = new mongoose.Schema({ - orcid : String, - name : { type: String, default: ''}, - level : { type: Number, default: 0 }, - lastseenAt : { type: Date, default: Date.now } + orcid: String, + name: {type: String, default: ''}, + level: {type: Number, default: 0}, + lastseenAt: {type: Date, default: Date.now} }); User.plugin(timestamps); diff --git a/lib/publisher.js b/lib/publisher.js new file mode 100644 index 0000000..f99b60a --- /dev/null +++ b/lib/publisher.js @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const Publisher = require('./model/publisher'); + +module.exports.addJournal = function (publisherId, journal) { + return new Promise((fulfill, reject) => { + Publisher.findOne({id: publisherId}, (err, publisher) => { + if (err || !publisher) { + reject("No publisher with this ID"); + } else { + if (journal.domains.every(d=> publisher.domains.includes(d))) { + publisher.journalCandidates.push(journal.id); + publisher.save(err => { + if (err) { + reject("Could not add journal to publisher"); + } else { + fulfill(); + } + }) + } else { + reject("Not all domains allowed for this publisher"); + } + } + }); + }); +} + +module.exports.includesJournal = async function (journalId) { + Publisher.findOne({journals: journalId}, (err, publisher) => { + if (err || !publisher) + return false; + + return publisher; + }); +} diff --git a/lib/re3data/getMetrics.py b/lib/re3data/getMetrics.py new file mode 100644 index 0000000..ed791f6 --- /dev/null +++ b/lib/re3data/getMetrics.py @@ -0,0 +1,54 @@ +import urllib.request +import json + +base_url = 'https://www.re3data.org/metrics/data/' +metrics = ['aidSystems', + 'apis', + 'certificates', + 'contentTypes', + 'dataAccess', + 'dataAccessRestrictions', + 'databaseAccess', + 'databaseAccessRestrictions', + 'databaseLicenses', + 'dataLicenses', + 'dataUploads', + 'dataUploadRestrictions', + 'enhancedPublication', + 'institutionCountry', + 'responsibilityTypes', + 'institutionType', + 'keywords', + 'metadataStandards', + 'pidSystems', + 'providerTypes', + 'qualityManagement', + 'repositoryLanguages', + 'software', + 'subjects', + 'syndications', + 'types', + 'versioning'] + +f = open('re3data-metrics.json', 'w') +f.write('{') + +for metric in metrics: + content = urllib.request.urlopen(base_url + metric) + data = content.read(); + encoding = content.info().get_content_charset('utf-8') + contentJson = json.loads(data.decode(encoding)) + f.write('"' + metric + '": ') + + term_list = [] + + for met in contentJson: + term_list.append(met['Terms']) + + f.write(json.dumps(term_list)) + if metric != metrics[len(metrics) - 1]: + f.write(', \n') + else: + f.write('\n') + +f.write('}') diff --git a/lib/re3data/re3data-metrics.json b/lib/re3data/re3data-metrics.json new file mode 100644 index 0000000..a6f2b45 --- /dev/null +++ b/lib/re3data/re3data-metrics.json @@ -0,0 +1,28 @@ +{"aidSystems": ["none", "orcid", "other", "authorclaim", "isni", "researcherid"], +"apis": ["REST", "FTP", "other", "OAI-PMH", "NetCDF", "SWORD", "SOAP", "OpenDAP", "SPARQL"], +"certificates": ["CoreTrustSeal", "WDS", "RatSWD", "CLARIN certificate B", "other", "DSA", "DINI Certificate", "DIN 31644", "Trusted Digital Repository"], +"contentTypes": ["Scientific and statistical data formats", "Standard office documents", "Images", "Raw data", "Plain text", "other", "Structured graphics", "Structured text", "Archived data", "Databases", "Audiovisual data", "Software applications", "Source code", "Networkbased data", "Configuration data"], +"dataAccess": ["open", "restricted", "embargoed", "closed"], +"dataAccessRestrictions": ["registration", "other", "feeRequired", "institutional membership"], +"databaseAccess": ["open", "restricted", "closed"], +"databaseAccessRestrictions": ["registration", "other", "feerequired"], +"databaseLicenses": ["CC", "other", "Copyrights", "CC0", "Apache License 2.0", "Public Domain", "ODC", "BSD"], +"dataLicenses": ["other", "Copyrights", "CC", "Public Domain", "CC0", "ODC", "OGL", "OGLC", "Apache License 2.0", "BSD", "RL", "none"], +"dataUploads": ["restricted", "closed", "open"], +"dataUploadRestrictions": ["registration", "other", "institutional membership", "feeRequired"], +"enhancedPublication": ["unknown", "yes", "no"], +"institutionCountry": ["usa", "deu", "gbr", "eec", "can", "aaa", "fra", "aus", "che", "jpn", "nld", "ind", "chn", "esp", "aut", "ita", "bel", "nor", "swe", "dnk", "rus", "zaf", "fin", "mex", "kor", "bra", "nzl", "pol", "col", "cze", "grc", "prt", "twn", "irl", "isr", "est", "arg", "sgp", "hun", "ltu", "idn", "ken", "svn", "tur", "grl", "hkg", "isl", "lux", "lva", "per", "srb", "ben", "bfa", "chl", "cyp", "gha", "hrv", "pan", "svk", "tha", "ukr", "aze", "civ", "cmr", "cri", "egy", "fji", "kaz", "lbn", "lka", "mkd", "nam", "ncl", "pak", "phl", "pyf", "rou", "sdn", "sen", "tun"], +"responsibilityTypes": ["general", "technical", "funding", "sponsoring"], +"institutionType": ["non-profit", "commercial"], +"keywords": ["multidisciplinary", "genomics", "bioinformatics", "FAIR", "genetics", "health", "biodiversity", "biology", "climate", "DNA", "meteorology", "atmosphere", "agriculture", "COVID-19", "environment", "statistics", "ecology", "climate change", "cancer", "economics", "hydrology", "education", "weather", "gene expression", "ecosystem", "human", "oceanography", "molecular biology", "proteomics", "proteins", "remote sensing", "climatology", "demography", "social sciences", "RNA", "geology", "corpora", "life sciences", "water", "geophysics", "population", "protein", "earth sciences", "ocean", "plants", "geography", "history", "bacteria", "census", "census data", "genes", "biochemistry", "fungi", "taxonomy", "disease", "energy", "mouse", "temperature", "medicine", "transportation", "business", "chemistry", "genome", "microdata", "satellite", "astronomy", "culture", "earthquake", "genomes", "survey", "systems biology", "biotechnology", "politics", "physical sciences", "astrophysics", "seismology", "maps", "animals", "archaeology", "earth science", "phenotype", "spectroscopy", "diseases", "metabolomics", "cryosphere", "economy", "environmental health", "epidemiology", "public health", "India", "birds", "environmental sciences", "humanities", "land use", "space weather", "topography", "botany", "geospatial data", "law", "linguistics", "music", "air quality", "bathymetry", "biomedicine", "clouds", "ecosystems", "environmental science", "forestry", "gene", "infrastructure", "insects", "mammals", "mass spectrometry", "morphology", "satellites", "sociology", "Canada", "brain", "deep sea", "demographics", "finance", "fisheries", "geodesy", "health care", "ozone", "physics", "biosphere", "clinical trials", "crystallography", "ionosphere", "nutrition", "salinity", "space", "survey data", "vegetation", "water quality", "drugs", "earth", "virus", "aerosols", "anthropology", "architecture", "chromosome", "clinical trial", "employment", "family", "genotype", "geosciences", "metabolism", "mortality", "poverty", "precipitation", "technology", "Africa", "Australia", "biogeochemistry", "cosmology", "engineering", "glaciology", "income", "measurement", "mental health", "meterology", "minerals", "molecules", "religion", "satellite data", "science", "simulation", "space sciences", "transcriptomics", "atmospheric science", "atomic physics", "cell", "computer science", "cultural heritage", "enzyme", "government", "groundwater", "ice", "seafloor", "sequences", "solar activity", "sustainability", "wildlife", "Asia", "archaea", "arts", "cell biology", "cohort study", "diabetes", "digital humanities", "drilling", "earth observation", "evolution", "galaxy", "greenhouse gases", "housing", "microbiology", "mutation", "protein binding", "rat", "tumor", "vertebrates", "wavelength", "BLAST", "aging", "air pollution", "algae", "animal", "blood", "boundaries", "cells", "chromosomes", "environmental monitoring", "hydrography", "land cover", "literature", "magnetosphere", "microorganisms", "migration", "natural gas", "neuroimaging", "oncology", "ontology", "pathways", "petrology", "pharmacology", "plant", "psychology", "public safety", "qualitative data", "research data", "samples", "sediment", "soil", "transport", "viruses", "Germany", "HIV", "ageing", "antibodies", "aquaculture", "arctic", "art", "biosamples", "buoys", "chemical biology", "crime", "cruises", "data libraries", "drug", "entomology", "enzymes", "epigenomics", "flora", "galaxies", "geochemistry", "geodynamics", "gravity", "homo sapiens", "human genome", "industry", "infectious disease", "land", "marine", "mineralogy", "natural resources", "organisms", "paleontology", "pathway", "phylogenetics", "plasmids", "political science", "radiation", "recreation", "reptiles", "sequencing", "space missions", "spectra", "stars", "tourism", "zoology", "Bioinformatics", "Europe", "GIS", "aerosol", "aerospace", "amino acids", "amphibians", "arctic regions", "atmospheric chemistry", "biological oceanography", "biological samples", "biomarker", "calibration", "clinical studies", "communication", "community", "computational biology"], +"metadataStandards": ["Dublin Core", "DataCite Metadata Schema", "DDI - Data Documentation Initiative", "ISO 19115", "Repository-Developed Metadata Schemas", "FGDC/CSDGM - Federal Geographic Data Committee Content Standard for Digital Geospatial Metadata", "DIF - Directory Interchange Format", "CF (Climate and Forecast) Metadata Conventions", "EML - Ecological Metadata Language", "other", "Darwin Core", "OAI-ORE - Open Archives Initiative Object Reuse and Exchange", "RDF Data Cube Vocabulary", "DCAT - Data Catalog Vocabulary", "ABCD - Access to Biological Collection Data", "ISA-Tab", "FITS - Flexible Image Transport System", "International Virtual Observatory Alliance Technical Specifications", "CIF - Crystallographic Information Framework", "MIBBI - Minimum Information for Biological and Biomedical Investigations", "PROV", "SDMX - Statistical Data and Metadata Exchange", "SPASE Data Model", "CIM - Common Information Model", "AVM - Astronomy Visualization Metadata", "MIDAS-Heritage", "CSMD-CCLRC Core Scientific Metadata Model", "Genome Metadata"], +"pidSystems": ["none", "doi", "hdl", "other", "urn", "purl", "ark"], +"providerTypes": ["dataprovider", "serviceprovider"], +"qualityManagement": ["yes", "unknown", "no"], +"repositoryLanguages": ["eng", "deu", "fra", "spa", "zho", "jpn", "rus", "por", "nld", "ita", "swe", "pol", "dan", "fin", "cat", "ara", "ces", "kor", "ell", "hun", "nor", "ind", "est", "lav", "hin", "lit", "tur", "slk", "slv", "isl", "ron", "tha", "ukr", "fas", "hrv", "nob", "srp", "vie", "bul", "eus", "gle", "heb", "mlt", "nno", "afr", "ben", "cym", "guj", "kal", "kan", "lao", "mal", "mar", "roh", "sqi", "srd", "tam", "tel", "twi", "yor"], +"software": ["unknown", "other", "DataVerse", "DSpace", "MySQL", "CKAN", "Fedora", "EPrints", "Nesstar", "DigitalCommons", "eSciDoc", "Opus", "dLibra"], +"subjects": ["2 Life Sciences", "3 Natural Sciences", "1 Humanities and Social Sciences", "21 Biology", "34 Geosciences (including Geography)", "22 Medicine", "201 Basic Biological and Medical Research", "4 Engineering Sciences", "12 Social and Behavioural Sciences", "313 Atmospheric Science and Oceanography", "205 Medicine", "11 Humanities", "32 Physics", "111 Social Sciences", "315 Geophysics and Geodesy", "203 Zoology", "20105 General Genetics", "31 Chemistry", "204 Microbiology, Virology and Immunology", "202 Plant Sciences", "112 Economics", "23 Agriculture, Forestry, Horticulture and Veterinary Medicine", "311 Astrophysics and Astronomy", "20107 Bioinformatics and Theoretical Biology", "31302 Oceanography", "317 Geography", "44 Computer Science, Electrical and System Engineering", "318 Water Research", "20502 Public Health, Health Services Research, Social Medicine", "31301 Atmospheric Science", "207 Agriculture, Forestry, Horticulture and Veterinary Medicine", "20503 Human Genetics", "104 Linguistics", "11102 Empirical Social Research", "102 History", "31502 Geodesy, Photogrammetry, Remote Sensing, Geoinformatics, Cartogaphy", "20306 Animal Genetics, Cell and Developmental Biology", "316 Geochemistry, Mineralogy and Crystallography", "409 Computer Science", "20103 Cell Biology", "20303 Animal Ecology, Biodiversity and Ecosystem Research", "206 Neurosciences", "314 Geology and Palaeontology", "103 Fine Arts, Music, Theatre and Media Studies", "308 Optics, Quantum Optics and Physics of Atoms, Molecules and Plasmas", "20101 Biochemistry", "20202 Plant Ecology and Ecosystem Analysis", "20207 Plant Genetics", "101 Ancient Cultures", "31501 Geophysics", "109 Education Sciences", "11205 Statistics and Econometrics", "20501 Epidemiology, Medical Biometry, Medical Informatics", "11104 Political Science", "11202 Economic and Social Policy", "309 Particles, Nuclei and Fields", "113 Jurisprudence", "31801 Hydrogeology, Hydrology, Limnology, Urban Water Management, Water Chemistry, Integrated Water Resources Management", "45 Construction Engineering and Architecture", "106 Non-European Languages and Cultures, Social and Cultural Anthropology, Jewish Studies and Religious Studies", "20104 Structural Biology", "301 Molecular Chemistry", "43 Materials Science and Engineering", "410 Construction Engineering and Architecture", "110 Psychology", "20401 Metabolism, Biochemistry and Genetics of Microorganisms", "20701 Soil Sciences", "303 Physical and Theoretical Chemistry", "33 Mathematics", "20704 Ecology of Agricultural Landscapes", "31101 Astrophysics and Astronomy", "105 Literary Studies", "20509 Pharmacology", "307 Condensed Matter Physics", "31702 Human Geography", "406 Materials Science", "407 Systems Engineering", "40904 Artificial Intelligence, Image and Language Processing", "20710 Basic Forest Research", "30301 Physical Chemistry of Molecules, Interfaces and Liquids - Spectroscopy, Kinetics", "31701 Physical Geography", "20405 Immunology", "304 Analytical Chemistry, Method Development (Chemistry)", "20302 Evolution, Anthropology", "302 Chemical Solid State and Surface Research", "305 Biological Chemistry and Food Chemistry", "107 Theology", "30801 Optics, Quantum Optics, Atoms, Molecules, Plasmas", "10301 Art History", "10601 Social and Cultural Anthropology and Ethnology/Folklore", "20404 Virology", "31601 Geochemistry, Mineralogy and Crystallography", "10203 Modern and Current History", "10302 Musicology", "20708 Agricultural Economics and Sociology", "42 Thermal Engineering/Process Engineering", "20403 Medical Microbiology, Molecular Infection Biology", "20606 Cognitive Neuroscience and Neuroimaging", "31401 Geology and Palaeontology", "41002 Urbanism, Spatial Planning, Transportation and Infrastructure Planning, Landscape Planning", "20201 Plant Systematics and Evolution", "20514 Hematology, Oncology, Transfusion Medicine", "20532 Biomedical Technology and Medical Physics", "41001 Architecture, Building and Construction History, Sustainable Building Technology, Building Design", "11103 Communication Science", "11203 Public Finance", "11204 Business Administration", "20301 Systematics and Morphology", "408 Electrical Engineering", "10104 Classical Archaeology", "20508 Pharmacy", "20709 Inventory Control and Use of Forest Resources", "40704 Traffic and Transport Systems, Logistics", "11206 Economic and Social History", "20108 Anatomy", "20510 Toxicology and Occupational Medicine", "20102 Biophysics", "20205 Plant Biochemistry and Biophysics", "20504 Physiology", "30901 Particles, Nuclei and Fields", "10204 History of Science", "10902 Research on Teaching, Learning and Training", "20520 Pediatric and Adolescent Medicine", "403 Process Engineering, Technical Chemistry", "10605 Religious Studies and Jewish Studies", "10903 Research on Socialization and Educational Institutions and Professions", "11301 Legal and Political Philosophy, Legal History, Legal Theory", "20305 Biochemistry and Animal Physiology", "20402 Microbial Ecology and Applied Microbiology", "20530 Radiology and Nuclear Medicine", "20604 Systemic Neuroscience, Computational Neuroscience, Behaviour", "20705 Plant Breeding", "30102 Organic Molecular Chemistry", "404 Heat Energy Technology, Thermal Machines, Fluid Mechanics", "405 Materials Engineering", "41 Mechanical and industrial Engineering", "10201 Medieval History", "10403 Typology, Non-European Languages, Historical Linguistics", "108 Philosophy", "11303 Public Law", "20702 Plant Cultivation", "40902 Software Technology", "10202 Early Modern History", "20506 Pathology and Forensic Medicine", "20515 Gastroenterology, Metabolism", "20707 Agricultural and Food Process Engineering", "20713 Basic Veterinary Medical Science", "30202 Physical Chemistry of Solids and Surfaces, Material Characterisation", "312 Mathematics", "10101 Prehistory", "10103 Ancient History", "10105 Egyptology and Ancient Near Eastern Studies", "11305 Criminology", "20601 Molecular Neuroscience and Neurogenetics", "30101 Inorganic Molecular Chemistry", "30203 Theory and Modelling", "10303 Theatre and Media Studies", "10603 African, American and Oceania Studies", "20304 Sensory and Behavioural Biology", "20513 Pneumology, Clinical Infectiology Intensive Care Medicine", "20521 Gynaecology and Obstetrics", "20531 Radiation Oncology and Radiobiology", "30201 Solid State and Surface Chemistry, Material Synthesis", "30501 Biological and Biomimetic Chemistry", "310 Statistical Physics, Soft Matter, Biological Physics, Nonlinear Dynamics", "402 Mechanics and Constructive Mechanical Engineering", "40204 Acoustics", "40705 Human Factors, Ergonomics, Human-Machine Systems", "40803 Electrical Energy Generation, Distribution, Application", "10401 General and Applied Linguistics", "10402 Individual Linguistics", "10604 Islamic Studies, Arabian Studies, Semitic Studies", "11004 Differential Psychology, Clinical Psychology, Medical Psychology, Methodology", "20106 Developmental Biology", "20206 Plant Cell and Developmental Biology", "20505 Nutritional Sciences", "20517 Endocrinology, Diabetology", "20711 Animal Husbandry, Breeding and Hygiene", "20714 Basic Research on Pathogenesis, Diagnostics and Therapy and Clinical Veterinary Medicine", "30302 General Theoretical Chemistry", "30401 Analytical Chemistry, Method Development (Chemistry)", "306 Polymer Research", "40903 Operating, Communication and Information Systems", "10503 European and American Literature", "10602 Asian Studies", "10701 Protestant Theology", "10901 General Education and History of Education", "11002 Developmental and Educational Psychology", "11003 Social Psychology, Industrial and Organisational Psychology", "11101 Sociological Theory", "20204 Plant Physiology", "20507 Clinical Chemistry and Pathobiochemistry", "20518 Rheumatology, Clinical Immunology, Allergology", "20524 Gerontology and Geriatric Medicine", "20611 Clinical Neurosciences III - Ophthalmology", "20703 Plant Nutrition", "30701 Experimental Condensed Matter Physics", "40401 Energy Process Engineering", "40802 Communication, High-Frequency and Network Technology, Theoretical Electrical Engineering", "41004 Sructural Engineering, Building Informatics, Construction Operation", "41006 Geotechnics, Hydraulic Engineering", "10504 General and Comparative Literature and Cultural Studies", "10702 Roman Catholic Theology", "11001 General, Biological and Mathematical Psychology", "11201 Economic Theory", "11304 Criminal Law and Law of Criminal Procedure", "20512 Cardiology, Angiology", "20519 Dermatology", "20522 Reproductive Medicine/Biology", "20528 Dentistry, Oral Surgery", "30502 Food Chemistry", "30601 Preparatory and Physical Chemistry of Polymers", "30603 Polymer Materials", "40304 Biological Process Engineering", "40502 Sintered Metallic and Ceramic Materials", "40503 Composite Materials", "40601 Thermodynamics and Kinetics of Materials", "40605 Biomaterials", "40701 Automation, Control Systems, Robotics, Mechatronics", "10102 Classical Philology", "10501 Medieval German Literature", "10801 History of Philosophy", "11302 Private Law", "20203 Inter-organismic Interactions of Plants", "20523 Urology", "20526 Cardiothoracic Surgery", "20527 Traumatology and Orthopaedics", "20602 Cellular Neuroscience", "20603 Developmental Neurobiology", "20605 Comparative Neurobiology", "20608 Clinical Neurosciences I - Neurology, Neurosurgery", "20609 Biological Psychiatry", "30602 Experimental and Theoretical Physics of Polymers", "30702 Theoretical Condensed Matter Physics", "31001 Statistical Physics, Soft Matter, Biological Physics, Nonlinear Dynamics", "31201 Mathematics", "40301 Chemical and Thermal Process Engineering", "40302 Technical Chemistry", "40402 Technical Thermodynamics", "40501 Metallurgical and Thermal Processes, Thermomechanical Treatment of Materials", "40603 Microstructural Mechanical Properties of Materials", "40702 Measurement Systems", "40801 Electronic Semiconductors, Components, Circuits, Systems", "40901 Theoretical Computer Science", "41003 Construction Material Sciences, Chemistry, Building Physics"], +"syndications": ["rss", "atom", "other"], +"types": ["disciplinary", "institutional", "other"], +"versioning": ["yes", "no"] +} \ No newline at end of file diff --git a/lib/steps.js b/lib/steps.js index 4e5a55c..bff023c 100644 --- a/lib/steps.js +++ b/lib/steps.js @@ -966,6 +966,7 @@ function save(passon) { var compendium = new Compendium({ id: passon.id, user: passon.user, + journal: '', metadata: passon.metadata, candidate: true, bag: passon.isBag, diff --git a/package-lock.json b/package-lock.json index ff3410b..9d02b5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,9 +80,9 @@ "dev": true }, "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz", + "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -1205,6 +1205,45 @@ "restore-cursor": "^2.0.0" } }, + "cli-progress": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.9.0.tgz", + "integrity": "sha512-g7rLWfhAo/7pF+a/STFH/xPyosaL1zgADhI0OM83hl3c7S43iGvJWEAV2QuDOnQ8i6EMBj/u4+NTd0d5L+4JfA==", + "requires": { + "colors": "^1.1.2", + "string-width": "^4.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -1434,9 +1473,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.12.1.tgz", + "integrity": "sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw==" }, "core-util-is": { "version": "1.0.2", @@ -1796,8 +1835,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", @@ -1823,9 +1861,8 @@ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" }, "erc-checker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/erc-checker/-/erc-checker-1.3.0.tgz", - "integrity": "sha512-JeBDjsS2JNYsX+jwAcfUAfpiPAMmbc4ce3Svw4GluSu5kHsE628biW4tIj1TTU4AQhdvXX0+tKqQuWES+4OHXA==", + "version": "github:o2r-project/erc-checker#db32156b0e95bfab06c3db5c1283e47737534900", + "from": "github:o2r-project/erc-checker#v1.3.0", "requires": { "ajv": "^5.5.2", "base64-arraybuffer": "^0.2.0", @@ -1844,29 +1881,6 @@ "pngjs": "^3.4.0", "promise": "^8.1.0", "rewire": "^4.0.1" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - } } }, "error-ex": { @@ -3041,12 +3055,12 @@ } }, "global": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", - "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", "requires": { "min-document": "^2.19.0", - "process": "~0.5.1" + "process": "^0.11.10" } }, "globals": { @@ -3232,9 +3246,9 @@ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==" + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "image-size": { "version": "0.8.3", @@ -3719,9 +3733,9 @@ } }, "load-bmfont": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.0.tgz", - "integrity": "sha512-kT63aTAlNhZARowaNYcY29Fn/QYkc52M3l6V1ifRcPewg2lvUZDAj7R6dXjOL9D0sict76op3T5+odumDSF81g==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", "requires": { "buffer-equal": "0.0.1", "mime": "^1.3.4", @@ -4720,11 +4734,22 @@ "integrity": "sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==" }, "object-sizeof": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-1.6.0.tgz", - "integrity": "sha512-Z+suoK94o8WjEMYItJjpPyG6I78STcI1CxM7rjxM8LhbgegAY+jhGSFY0c5Ez20LXgDGtyKzYZNXHpDyr6hj8Q==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-1.6.1.tgz", + "integrity": "sha512-gNKGcRnDRXwEpAdwUY3Ef+aVZIrcQVXozSaVzHz6Pv4JxysH8vf5F+nIgsqW5T/YNwZNveh0mIW7PEH1O2MrDw==", "requires": { - "buffer": "^5.5.0" + "buffer": "^5.6.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } } }, "object.assign": { @@ -5028,9 +5053,9 @@ "dev": true }, "pixelmatch": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.0.tgz", - "integrity": "sha512-TdleROuanI+Uo/4PcOAH7b7qgO4kjzJO8K4y/TBAo1wx/5BE8cn1B0I6Jfk3mKcJsGpWvX7zjM8OjU5o9i+aog==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", + "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", "requires": { "pngjs": "^4.0.1" }, @@ -5082,9 +5107,9 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "process": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.1", @@ -5132,9 +5157,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "pump": { "version": "3.0.0", @@ -5191,6 +5216,11 @@ "inherits": "~2.0.3" } }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -5278,9 +5308,9 @@ } }, "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "regexp-clone": { "version": "1.0.0", @@ -5424,17 +5454,6 @@ "eslint": "^4.19.1" }, "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", @@ -5447,9 +5466,9 @@ } }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" } @@ -5497,34 +5516,17 @@ "strip-json-comments": "~2.0.1", "table": "4.0.2", "text-table": "~0.2.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, "ignore": { "version": "3.3.10", "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "readable-stream": { "version": "2.3.7", @@ -5539,6 +5541,11 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -5559,9 +5566,12 @@ } }, "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } }, "rx-lite": { "version": "4.0.8", @@ -5948,29 +5958,6 @@ "lodash": "^4.17.4", "slice-ansi": "1.0.0", "string-width": "^2.1.1" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - } } }, "tar": { @@ -6107,9 +6094,9 @@ "integrity": "sha1-xm08Im7FOuSPVL3APEAv6YQGR/4=" }, "timm": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/timm/-/timm-1.6.2.tgz", - "integrity": "sha512-IH3DYDL1wMUwmIlVmMrmesw5lZD6N+ZOAFWEyLrtpoL9Bcrs9u7M/vyOnHzDD2SMs4irLkVjqxZbHrXStS/Nmw==" + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" }, "tiny-json-http": { "version": "7.3.0", @@ -6117,9 +6104,9 @@ "integrity": "sha512-dZrf9ZGZQhe8QhhPN3o4uDCQuBc3Gaq4CtbU/67hQDWXuvjLI1mayr8AOFSiUGa3F818SpIgUnC3mM673VRHGQ==" }, "tinycolor2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", - "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" }, "tmp": { "version": "0.1.0", @@ -6491,11 +6478,11 @@ } }, "xhr": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", - "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", "requires": { - "global": "~4.3.0", + "global": "~4.4.0", "is-function": "^1.0.1", "parse-headers": "^2.0.0", "xtend": "^4.0.0" diff --git a/package.json b/package.json index db6f232..d9e8a8e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "test": "DEBUG=*,-modem,-mocha:* mocha", "test_ci": "DEBUG=*,-modem,-mocha:* mocha ./test/$TEST_SET*.js --exit", "test_ci_async_dump": "DEBUG=*,-modem,-mocha:* mocha ./test/ --require test/async-dump", - "test_clean_upload_cache": "rm /tmp/o2r-*.zip" + "test_clean_upload_cache": "rm /tmp/o2r-*.zip", + "test_publisher": "DEBUG=*,-modem,-mocha:* mocha ./test/publisher.js" }, "author": "o2r-project (https://o2r.info)", "license": "Apache-2.0", @@ -37,6 +38,7 @@ "erc-checker": "o2r-project/erc-checker#v1.3.0", "express": "^4.17.1", "express-session": "^1.17.0", + "fast-xml-parser": "^3.19.0", "filesize": "^6.1.0", "fs-extra": "^8.1.0", "htmlparser2": "^4.1.0", @@ -53,6 +55,7 @@ "nodemailer": "^6.4.5", "object-path": "^0.11.5", "passport": "^0.4.1", + "psl": "^1.8.0", "randomstring": "^1.1.5", "recursive-readdir": "^2.2.2", "request": "^2.88.2", diff --git a/test/journal.js b/test/journal.js new file mode 100644 index 0000000..926f214 --- /dev/null +++ b/test/journal.js @@ -0,0 +1,710 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* eslint-env mocha */ +const assert = require('chai').assert; +const request = require('request'); +const path = require('path'); +const mongojs = require('mongojs'); +const fs = require('fs'); +const config = require('../config/config'); +const chai = require('chai'); +chai.use(require('chai-datetime')); +const createCompendiumPostRequest = require('./util').createCompendiumPostRequest; +const publishCandidate = require('./util').publishCandidate; + +require("./setup"); + +const cookie_o2r = 's:C0LIrsxGtHOGHld8Nv2jedjL4evGgEHo.GMsWD5Vveq0vBt7/4rGeoH5Xx7Dd2pgZR9DvhKCyDTY'; +const cookie_plain = 's:yleQfdYnkh-sbj9Ez--_TWHVhXeXNEgq.qRmINNdkRuJ+iHGg5woRa9ydziuJ+DzFG9GnAZRvaaM'; +const cookie_editor = 's:xWHihqZq6jEAObwbfowO5IwdnBxohM7z.VxqsRC5A1VqJVspChcxVPuzEKtRE+aKLF8k3nvCcZ8g'; +const cookie_admin = 's:hJRjapOTVCEvlMYCb8BXovAOi2PEOC4i.IEPb0lmtGojn2cVk2edRuomIEanX6Ddz87egE5Pe8UM'; + +describe('Create journal', () => { + var db = mongojs('localhost/muncher', ['journal']); + + before(function (done) { + db.journal.drop(function (err, doc) { + done(); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin', () => { + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('should return status code 400 without domains parameter', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + } + }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 400); + done(); + }); + }); + + it('should return status code 400 without name parameter', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 400); + done(); + }); + }); + }); + + describe('as editor', () => { + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as a known user', () => { + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_o2r); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); + + describe('as a user', () => { + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_plain); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); +}); + +describe('Add domain to journal', () => { + var db = mongojs('localhost/muncher', ['journal']); + + before(function (done) { + db.journal.drop(function () { + done(); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin', () => { + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + done(); + }); + }); + + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/' + journal_id + '/adddomain', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + url: "google.com", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as unauthorised user', () => { + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + done(); + }); + }); + + it('should return status code 403', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/' + journal_id + '/adddomain', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + url: "google.com", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 403); + done(); + }); + }); + }); +}); + +describe('Remove domain from journal', () => { + var db = mongojs('localhost/muncher', ['journal']); + + before(function (done) { + db.journal.drop(function () { + done(); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin', () => { + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + done(); + }); + }); + + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/' + journal_id + '/removedomain', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + url: "www.doi.pangaea.de", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as unauthorised user', () => { + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + done(); + }); + }); + + it('should return status code 403', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/' + journal_id + '/removedomain', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + url: "www.doi.pangaea.de", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 403); + done(); + }); + }); + }); +}); + +describe('Add journal to publisher', () => { + var db = mongojs('localhost/muncher', ['publisher journal']); + + before(function (done) { + db.publisher.drop(function () { + db.journal.drop(function () { + done(); + }); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin or editor', () => { + let publisher_id = null; + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + done(); + }); + }); + }); + + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/' + journal_id + '/addtopublisher', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + publisher: publisher_id, + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as unauthorised user', () => { + let publisher_id = null; + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + done(); + }); + }); + }); + + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_plain); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/' + journal_id + '/addtopublisher', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + publisher: publisher_id, + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); +}); + +describe('Accept compendium for journal', () => { + var db = mongojs('localhost/muncher', ['publisher journal']); + + before(function (done) { + db.publisher.drop(function () { + db.journal.drop(function () { + done(); + }); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as editor and owner', () => { + let compendium_id = null; + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + this.timeout(90000); + createCompendiumPostRequest('./test/workspace/rmd-data', cookie_o2r, 'workspace', (req) => { + request(req, (err, res, body) => { + compendium_id = JSON.parse(body).id; + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_o2r); + j.setCookie(ck, global.test_host); + request({ + uri: global.test_host + '/api/v1/compendium/' + compendium_id + '/journal', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + journal: journal_id, + } + }, () => { + console.log("Calling done") + done(); + }); + }); + }); + }); + }); + + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/' + journal_id + '/acceptcompendium', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + compendium: compendium_id, + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as unauthorised user', () => { + let compendium_id = null; + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + this.timeout(90000); + createCompendiumPostRequest('./test/workspace/rmd-data', cookie_o2r, 'workspace', (req) => { + request(req, (err, res, body) => { + compendium_id = JSON.parse(body).id; + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_o2r); + j.setCookie(ck, global.test_host); + request({ + uri: global.test_host + '/api/v1/compendium/' + compendium_id + '/journal', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + journal: journal_id, + } + }, () => { + done(); + }); + }); + }); + }); + }); + + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_o2r); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/journal/' + journal_id + '/acceptcompendium', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + compendium: compendium_id, + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); +}); diff --git a/test/publisher.js b/test/publisher.js new file mode 100644 index 0000000..fa92fb3 --- /dev/null +++ b/test/publisher.js @@ -0,0 +1,719 @@ +/* + * (C) Copyright 2017 o2r project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* eslint-env mocha */ +const assert = require('chai').assert; +const request = require('request'); +const path = require('path'); +const mongojs = require('mongojs'); +const fs = require('fs'); +const config = require('../config/config'); +const chai = require('chai'); +chai.use(require('chai-datetime')); +const createCompendiumPostRequest = require('./util').createCompendiumPostRequest; +const publishCandidate = require('./util').publishCandidate; + +require("./setup"); +const {joinURL} = require("webdav/dist/node/tools/url"); + +const cookie_o2r = 's:C0LIrsxGtHOGHld8Nv2jedjL4evGgEHo.GMsWD5Vveq0vBt7/4rGeoH5Xx7Dd2pgZR9DvhKCyDTY'; +const cookie_plain = 's:yleQfdYnkh-sbj9Ez--_TWHVhXeXNEgq.qRmINNdkRuJ+iHGg5woRa9ydziuJ+DzFG9GnAZRvaaM'; +const cookie_editor = 's:xWHihqZq6jEAObwbfowO5IwdnBxohM7z.VxqsRC5A1VqJVspChcxVPuzEKtRE+aKLF8k3nvCcZ8g'; +const cookie_admin = 's:hJRjapOTVCEvlMYCb8BXovAOi2PEOC4i.IEPb0lmtGojn2cVk2edRuomIEanX6Ddz87egE5Pe8UM'; + +describe('Create publisher', () => { + var db = mongojs('localhost/muncher', ['publisher']); + + before(function (done) { + db.publisher.drop(function () { + done(); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin', () => { + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('should return status code 400 without domains parameter', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 400); + done(); + }); + }); + + it('should return status code 400 without name parameter', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 400); + done(); + }); + }); + }); + + describe('as editor', () => { + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); + + describe('as a known user', () => { + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_o2r); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); + + describe('as a user', () => { + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_plain); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); +}); + +describe('Add domain to publisher', () => { + var db = mongojs('localhost/muncher', ['publisher']); + + before(function (done) { + db.publisher.drop(function () { + done(); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin', () => { + let publisher_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + done(); + }); + }); + + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/adddomain', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + url: "google.com", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as unauthorised user', () => { + let publisher_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + done(); + }); + }); + + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/adddomain', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + url: "google.com", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); +}); + +describe('Remove domain from publisher', () => { + var db = mongojs('localhost/muncher', ['publisher']); + + before(function (done) { + db.publisher.drop(function () { + done(); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin', () => { + let publisher_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + done(); + }); + }); + + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/removedomain', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + url: "www.doi.pangaea.de", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as unauthorised user', () => { + let publisher_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + done(); + }); + }); + + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/removedomain', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + url: "www.doi.pangaea.de", + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); +}); + +describe('Add journal to publisher', () => { + var db = mongojs('localhost/muncher', ['publisher journal']); + + before(function (done) { + db.publisher.drop(function () { + db.journal.drop(function () { + done(); + }); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin', () => { + let publisher_id = null; + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + done(); + }); + }); + }); + + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/addjournal', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + journalId: journal_id, + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as unauthorised user', () => { + let publisher_id = null; + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + done(); + }); + }); + }); + + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/addjournal', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + journalId: journal_id, + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); +}); + +describe('Remove journal from publisher', () => { + var db = mongojs('localhost/muncher', ['publisher journal']); + + before(function (done) { + db.publisher.drop(function () { + db.journal.drop(function () { + done(); + }); + }); + }); + + after(function (done) { + db.close(); + done(); + }); + + describe('as admin', () => { + let publisher_id = null; + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/addjournal', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + journalId: journal_id + } + }, () => { + done(); + }); + }); + }); + }); + + it('should return status code 200', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/removejournal', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + journalId: journal_id, + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + }); + + describe('as unauthorised user', () => { + let publisher_id = null; + let journal_id = null; + before(function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_admin); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + publisher_id = body.id; + request({ + uri: global.test_host + '/api/v1/journal/', + method: 'POST', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + name: "first", + domains: ["www.doi.pangaea.de", "www.zenodo.com"] + } + }, (err, res, body) => { + journal_id = body.id; + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/addjournal', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + journalId: journal_id + } + }, () => { + done(); + }); + }); + }); + }); + + it('should return status code 401', function (done) { + j = request.jar(); + ck = request.cookie('connect.sid=' + cookie_editor); + j.setCookie(ck, global.test_host); + + request({ + uri: global.test_host + '/api/v1/publisher/' + publisher_id + '/removejournal', + method: 'PUT', + header: { + "content-type": "application/json" + }, + jar: j, + json: { + journalId: journal_id, + } + }, (err, res) => { + assert.ifError(err); + assert.equal(res.statusCode, 401); + done(); + }); + }); + }); +}); diff --git a/test_journal_results.txt b/test_journal_results.txt new file mode 100644 index 0000000..ca45f33 --- /dev/null +++ b/test_journal_results.txt @@ -0,0 +1,35 @@ + + + Create journal + as admin + ✔ should return status code 200 (53ms) + ✔ should return status code 400 without domains parameter + ✔ should return status code 400 without name parameter + as editor + ✔ should return status code 200 + as a known user + ✔ should return status code 401 + as a user + ✔ should return status code 401 + + Add domain to journal + as admin + ✔ should return status code 200 + as unauthorised user + ✔ should return status code 403 + + Remove domain from journal + as admin + ✔ should return status code 200 + as unauthorised user + ✔ should return status code 403 + + Add journal to publisher + as admin or editor + ✔ should return status code 200 + as unauthorised user + ✔ should return status code 401 + + + 12 passing (3s) + diff --git a/test_publisher_results.txt b/test_publisher_results.txt new file mode 100644 index 0000000..652f4e5 --- /dev/null +++ b/test_publisher_results.txt @@ -0,0 +1,41 @@ + + + Create publisher + as admin + ✔ should return status code 200 (124ms) + ✔ should return status code 400 without domains parameter + ✔ should return status code 400 without name parameter + as editor + ✔ should return status code 401 + as a known user + ✔ should return status code 401 + as a user + ✔ should return status code 401 + + Add domain to publisher + as admin + ✔ should return status code 200 + as unauthorised user + ✔ should return status code 401 + + Remove domain from publisher + as admin + ✔ should return status code 200 + as unauthorised user + ✔ should return status code 401 + + Add journal to publisher + as admin + ✔ should return status code 200 + as unauthorised user + ✔ should return status code 401 + + Remove journal from publisher + as admin + ✔ should return status code 200 + as unauthorised user + ✔ should return status code 401 + + + 14 passing (3s) +