diff --git a/controllers/bulk.js b/controllers/bulk.js index 35e7fcb..f3dda31 100644 --- a/controllers/bulk.js +++ b/controllers/bulk.js @@ -18,16 +18,19 @@ import { _contextid, ObjectID, createExpressError, getAgentClaim, parseDocumentI const bulkCreate = async function (req, res, next) { res.set("Content-Type", "application/json; charset=utf-8") const documents = req.body - let err = {} if (!Array.isArray(documents)) { - err.message = "The request body must be an array of objects." - err.status = 400 + const err = { + message: "The request body must be an array of objects.", + status: 400 + } next(createExpressError(err)) return } if (documents.length === 0) { - err.message = "No action on an empty array." - err.status = 400 + const err = { + message: "No action on an empty array.", + status: 400 + } next(createExpressError(err)) return } @@ -44,8 +47,10 @@ const bulkCreate = async function (req, res, next) { if(idcheck) return d }) if (gatekeep.length > 0) { - err.message = "All objects in the body of a `/bulkCreate` must be JSON and must not contain a declared identifier property." - err.status = 400 + const err = { + message: "All objects in the body of a `/bulkCreate` must be JSON and must not contain a declared identifier property.", + status: 400 + } next(createExpressError(err)) return } @@ -64,28 +69,27 @@ const bulkCreate = async function (req, res, next) { // } // unordered bulkWrite() operations have better performance metrics. - let bulkOps = [] + const bulkOps = [] const generatorAgent = getAgentClaim(req, next) - for(let d of documents) { + for(const d of documents) { // Do not create empty {}s if(Object.keys(d).length === 0) continue const providedID = d?._id const id = isValidID(providedID) ? providedID : ObjectID() - d = utils.configureRerumOptions(generatorAgent, d) + const configuredDoc = utils.configureRerumOptions(generatorAgent, d) // id is also protected in this case, so it can't be set. - if(_contextid(d["@context"])) delete d.id - d._id = id - d['@id'] = `${process.env.RERUM_ID_PREFIX}${id}` - bulkOps.push({ insertOne : { "document" : d }}) + if(_contextid(configuredDoc["@context"])) delete configuredDoc.id + configuredDoc._id = id + configuredDoc['@id'] = `${process.env.RERUM_ID_PREFIX}${id}` + bulkOps.push({ insertOne : { "document" : configuredDoc }}) } try { - let dbResponse = await db.bulkWrite(bulkOps, {'ordered':false}) + const dbResponse = await db.bulkWrite(bulkOps, {'ordered':false}) res.set("Content-Type", "application/json; charset=utf-8") res.set("Link",dbResponse.result.insertedIds.map(r => `${process.env.RERUM_ID_PREFIX}${r._id}`)) // https://www.rfc-editor.org/rfc/rfc5988 res.status(201) const estimatedResults = bulkOps.map(f=>{ - let doc = f.insertOne.document - doc = idNegotiation(doc) + const doc = idNegotiation(f.insertOne.document) return doc }) res.json(estimatedResults) // https://www.rfc-editor.org/rfc/rfc7231#section-6.3.2 @@ -106,17 +110,20 @@ const bulkCreate = async function (req, res, next) { const bulkUpdate = async function (req, res, next) { res.set("Content-Type", "application/json; charset=utf-8") const documents = req.body - let err = {} - let encountered = [] + const encountered = [] if (!Array.isArray(documents)) { - err.message = "The request body must be an array of objects." - err.status = 400 + const err = { + message: "The request body must be an array of objects.", + status: 400 + } next(createExpressError(err)) return } if (documents.length === 0) { - err.message = "No action on an empty array." - err.status = 400 + const err = { + message: "No action on an empty array.", + status: 400 + } next(createExpressError(err)) return } @@ -134,13 +141,15 @@ const bulkUpdate = async function (req, res, next) { }) // The empty {}s will cause this error if (gatekeep.length > 0) { - err.message = "All objects in the body of a `/bulkUpdate` must be JSON and must contain a declared identifier property." - err.status = 400 + const err = { + message: "All objects in the body of a `/bulkUpdate` must be JSON and must contain a declared identifier property.", + status: 400 + } next(createExpressError(err)) return } // unordered bulkWrite() operations have better performance metrics. - let bulkOps = [] + const bulkOps = [] const generatorAgent = getAgentClaim(req, next) for(const objectReceived of documents){ // We know it has an id @@ -149,7 +158,7 @@ const bulkUpdate = async function (req, res, next) { // if(encountered.includes(idReceived)) continue encountered.push(idReceived) if(!idReceived.includes(process.env.RERUM_ID_PREFIX)) continue - let id = parseDocumentID(idReceived) + const id = parseDocumentID(idReceived) let originalObject try { originalObject = await db.findOne({"$or":[{"_id": id}, {"__rerum.slug": id}]}) @@ -159,16 +168,16 @@ const bulkUpdate = async function (req, res, next) { } if (null === originalObject) continue if (utils.isDeleted(originalObject)) continue - id = ObjectID() - let context = objectReceived["@context"] ? { "@context": objectReceived["@context"] } : {} - let rerumProp = { "__rerum": utils.configureRerumOptions(generatorAgent, originalObject, true, false)["__rerum"] } + const newId = ObjectID() + const context = objectReceived["@context"] ? { "@context": objectReceived["@context"] } : {} + const rerumProp = { "__rerum": utils.configureRerumOptions(generatorAgent, originalObject, true, false)["__rerum"] } delete objectReceived["__rerum"] delete objectReceived["_id"] delete objectReceived["@id"] // id is also protected in this case, so it can't be set. if(_contextid(objectReceived["@context"])) delete objectReceived.id delete objectReceived["@context"] - let newObject = Object.assign(context, { "@id": process.env.RERUM_ID_PREFIX + id }, objectReceived, rerumProp, { "_id": id }) + const newObject = Object.assign(context, { "@id": process.env.RERUM_ID_PREFIX + newId }, objectReceived, rerumProp, { "_id": newId }) bulkOps.push({ insertOne : { "document" : newObject }}) if(originalObject.__rerum.history.next.indexOf(newObject["@id"]) === -1){ originalObject.__rerum.history.next.push(newObject["@id"]) @@ -183,13 +192,12 @@ const bulkUpdate = async function (req, res, next) { } } try { - let dbResponse = await db.bulkWrite(bulkOps, {'ordered':false}) + const dbResponse = await db.bulkWrite(bulkOps, {'ordered':false}) res.set("Content-Type", "application/json; charset=utf-8") res.set("Link", dbResponse.result.insertedIds.map(r => `${process.env.RERUM_ID_PREFIX}${r._id}`)) // https://www.rfc-editor.org/rfc/rfc5988 res.status(200) const estimatedResults = bulkOps.filter(f=>f.insertOne).map(f=>{ - let doc = f.insertOne.document - doc = idNegotiation(doc) + const doc = idNegotiation(f.insertOne.document) return doc }) res.json(estimatedResults) // https://www.rfc-editor.org/rfc/rfc7231#section-6.3.2 diff --git a/controllers/crud.js b/controllers/crud.js index bce1179..4e0e636 100644 --- a/controllers/crud.js +++ b/controllers/crud.js @@ -17,20 +17,18 @@ const create = async function (req, res, next) { res.set("Content-Type", "application/json; charset=utf-8") let slug = "" if(req.get("Slug")){ - let slug_json = await generateSlugId(req.get("Slug"), next) + const slug_json = await generateSlugId(req.get("Slug"), next) if(slug_json.code){ next(createExpressError(slug_json)) return } - else{ - slug = slug_json.slug_id - } + slug = slug_json.slug_id } - let generatorAgent = getAgentClaim(req, next) - let context = req.body["@context"] ? { "@context": req.body["@context"] } : {} - let provided = JSON.parse(JSON.stringify(req.body)) - let rerumProp = { "__rerum": utils.configureRerumOptions(generatorAgent, provided, false, false)["__rerum"] } + const generatorAgent = getAgentClaim(req, next) + const context = req.body["@context"] ? { "@context": req.body["@context"] } : {} + const provided = JSON.parse(JSON.stringify(req.body)) + const rerumProp = { "__rerum": utils.configureRerumOptions(generatorAgent, provided, false, false)["__rerum"] } rerumProp.__rerum.slug = slug const providedID = provided._id const id = isValidID(providedID) ? providedID : ObjectID() @@ -40,16 +38,16 @@ const create = async function (req, res, next) { if(_contextid(provided["@context"])) delete provided.id delete provided["@context"] - let newObject = Object.assign(context, { "@id": process.env.RERUM_ID_PREFIX + id }, provided, rerumProp, { "_id": id }) + const newObject = Object.assign(context, { "@id": process.env.RERUM_ID_PREFIX + id }, provided, rerumProp, { "_id": id }) console.log("CREATE") try { - let result = await db.insertOne(newObject) + const result = await db.insertOne(newObject) res.set(utils.configureWebAnnoHeadersFor(newObject)) - newObject = idNegotiation(newObject) - newObject.new_obj_state = JSON.parse(JSON.stringify(newObject)) - res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"]) + const negotiatedObject = idNegotiation(newObject) + negotiatedObject.new_obj_state = JSON.parse(JSON.stringify(negotiatedObject)) + res.location(negotiatedObject[_contextid(negotiatedObject["@context"]) ? "id":"@id"]) res.status(201) - res.json(newObject) + res.json(negotiatedObject) } catch (error) { //MongoServerError from the client has the following properties: index, code, keyPattern, keyValue @@ -64,12 +62,12 @@ const create = async function (req, res, next) { * */ const query = async function (req, res, next) { res.set("Content-Type", "application/json; charset=utf-8") - let props = req.body + const props = req.body const limit = parseInt(req.query.limit ?? 100) const skip = parseInt(req.query.skip ?? 0) if (Object.keys(props).length === 0) { //Hey now, don't ask for everything...this can happen by accident. Don't allow it. - let err = { + const err = { message: "Detected empty JSON object. You must provide at least one property in the /query request body JSON.", status: 400 } @@ -93,9 +91,9 @@ const query = async function (req, res, next) { * */ const id = async function (req, res, next) { res.set("Content-Type", "application/json; charset=utf-8") - let id = req.params["_id"] + const id = req.params["_id"] try { - let match = await db.findOne({"$or": [{"_id": id}, {"__rerum.slug": id}]}) + const match = await db.findOne({"$or": [{"_id": id}, {"__rerum.slug": id}]}) if (match) { res.set(utils.configureWebAnnoHeadersFor(match)) //Support built in browser caching @@ -105,12 +103,12 @@ const id = async function (req, res, next) { // Include current version for optimistic locking const currentVersion = match.__rerum?.isOverwritten ?? "" res.set('Current-Overwritten-Version', currentVersion) - match = idNegotiation(match) - res.location(_contextid(match["@context"]) ? match.id : match["@id"]) - res.json(match) + const negotiatedMatch = idNegotiation(match) + res.location(_contextid(negotiatedMatch["@context"]) ? negotiatedMatch.id : negotiatedMatch["@id"]) + res.json(negotiatedMatch) return } - let err = { + const err = { "message": `No RERUM object with id '${id}'`, "status": 404 } diff --git a/controllers/gog.js b/controllers/gog.js index 67dd04d..76af936 100644 --- a/controllers/gog.js +++ b/controllers/gog.js @@ -28,22 +28,21 @@ const _gog_fragments_from_manuscript = async function (req, res, next) { const manID = req.body["ManuscriptWitness"] const limit = parseInt(req.query.limit ?? 50) const skip = parseInt(req.query.skip ?? 0) - let err = { message: `` } // This request can only be made my Gallery of Glosses production apps. if (agentID !== "61043ad4ffce846a83e700dd") { - err = Object.assign(err, { + const err = { message: `Only the Gallery of Glosses can make this request.`, status: 403 - }) + } + next(createExpressError(err)) + return } // Must have a properly formed body with a usable value - else if(!manID || !manID.startsWith("http")){ - err = Object.assign(err, { + if(!manID || !manID.startsWith("http")){ + const err = { message: `The body must be JSON like {"ManuscriptWitness":"witness_uri_here"}.`, status: 400 - }) - } - if (err.status) { + } next(createExpressError(err)) return } diff --git a/controllers/utils.js b/controllers/utils.js index 9de0c01..5c0106c 100644 --- a/controllers/utils.js +++ b/controllers/utils.js @@ -52,12 +52,12 @@ const idNegotiation = function (resBody) { const _id = resBody._id delete resBody._id if(!resBody["@context"]) return resBody - let modifiedResBody = JSON.parse(JSON.stringify(resBody)) + const modifiedResBody = JSON.parse(JSON.stringify(resBody)) const context = { "@context": resBody["@context"] } if(_contextid(resBody["@context"])) { delete resBody["@id"] delete resBody["@context"] - modifiedResBody = Object.assign(context, { "id": process.env.RERUM_ID_PREFIX + _id }, resBody) + return Object.assign(context, { "id": process.env.RERUM_ID_PREFIX + _id }, resBody) } return modifiedResBody } @@ -71,7 +71,7 @@ const idNegotiation = function (resBody) { * */ const generateSlugId = async function(slug_id="", next){ - let slug_return = {"slug_id":"", "code":0} + const slug_return = {"slug_id":"", "code":0} let slug if(slug_id){ slug_return.slug_id = slug_id @@ -100,7 +100,7 @@ const index = function (req, res, next) { } function createExpressError(err) { - let error = {} + const error = {} if (err.code) { switch (err.code) { case 11000: @@ -145,14 +145,13 @@ const remove = async function(id) { */ function getAgentClaim(req, next) { const claimKeys = [process.env.RERUM_AGENT_CLAIM, "http://devstore.rerum.io/v1/agent", "http://store.rerum.io/agent"] - let agent = "" for (const claimKey of claimKeys) { - agent = req.user[claimKey] + const agent = req.user[claimKey] if (agent) { return agent } } - let err = { + const err = { "message": "Could not get agent from req.user. Have you registered with RERUM?", "status": 403 } @@ -181,7 +180,7 @@ async function alterHistoryNext(objToUpdate, newNextID) { //We can keep this real short if we trust the objects sent into here. I think these are private helper functions, and so we can. if(objToUpdate.__rerum.history.next.indexOf(newNextID) === -1){ objToUpdate.__rerum.history.next.push(newNextID) - let result = await db.replaceOne({ "_id": objToUpdate["_id"] }, objToUpdate) + const result = await db.replaceOne({ "_id": objToUpdate["_id"] }, objToUpdate) return result.modifiedCount > 0 } return true @@ -196,9 +195,8 @@ async function alterHistoryNext(objToUpdate, newNextID) { * @throws Exception when a JSONObject with no '__rerum' property is provided. */ async function getAllVersions(obj) { - let ls_versions - let primeID = obj?.__rerum.history.prime - let rootObj = ( primeID === "root") + const primeID = obj?.__rerum.history.prime + const rootObj = ( primeID === "root") ? //The obj passed in is root. So it is the rootObj we need. JSON.parse(JSON.stringify(obj)) : //The obj passed in knows the ID of root, grab it from Mongo @@ -209,7 +207,7 @@ async function getAllVersions(obj) { * This is the because some of the @ids have different RERUM URL patterns on them. **/ //All the children of this object will have its @id in __rerum.history.prime - ls_versions = await db.find({ "__rerum.history.prime": rootObj['@id'] }).toArray() + const ls_versions = await db.find({ "__rerum.history.prime": { $eq: rootObj['@id'] } }).toArray() //The root object is a version, prepend it in ls_versions.unshift(rootObj) return ls_versions diff --git a/utils.js b/utils.js index 299d662..0468615 100644 --- a/utils.js +++ b/utils.js @@ -42,7 +42,7 @@ const configureRerumOptions = function(generator, received, update, extUpdate){ } else{ //We are either updating an existing RERUM object or creating a new one. - if(received_options.hasOwnProperty("history")){ + if(received_options?.history){ history = received_options.history if(update){ //This means we are configuring from the update action and we have passed in a clone of the originating object (with its @id) that contained a __rerum.history @@ -68,7 +68,7 @@ const configureRerumOptions = function(generator, received, update, extUpdate){ history_prime = "root" history_previous = "" } - if(received_options.hasOwnProperty("releases")){ + if(received_options?.releases){ releases = received_options.releases releases_previous = releases.previous } @@ -86,7 +86,7 @@ const configureRerumOptions = function(generator, received, update, extUpdate){ rerumOptions.alpha = true rerumOptions.APIversion = process.env.RERUM_API_VERSION //It is important for the cache workflow that these be properly formatted. - let creationDateTime = new Date(Date.now()).toISOString().replace("Z", "") + const creationDateTime = new Date(Date.now()).toISOString().replace("Z", "") rerumOptions.createdAt = creationDateTime rerumOptions.isOverwritten = "" rerumOptions.isReleased = "" @@ -101,18 +101,14 @@ const configureRerumOptions = function(generator, received, update, extUpdate){ * Check this object for deleted status. deleted objects in RERUM look like {"@id":"{some-id}", __deleted:{object properties}} */ const isDeleted = function(obj){ - return obj.hasOwnProperty("__deleted") + return obj?.__deleted !== undefined } /** * Check this object for released status. Released objects in RERUM look like {"@id":"{some-id}", __rerum:{"isReleased" : "ISO-DATE-TIME"}} */ const isReleased = function(obj){ - let bool = - (obj.hasOwnProperty("__rerum") && - obj.__rerum.hasOwnProperty("isReleased") && - obj.__rerum.isReleased !== "") - return bool + return obj?.__rerum?.isReleased !== undefined && obj.__rerum.isReleased !== "" } /** @@ -130,17 +126,17 @@ const isGenerator = function(origObj, changeAgent){ * return a JSON object. keys are header names, values are header values. */ const configureWebAnnoHeadersFor = function(obj){ - let headers = {} + const headers = {} if(isLD(obj)){ headers["Content-Type"] = "application/ld+json;charset=utf-8;profile=\"http://www.w3.org/ns/anno.jsonld\"" } if(isContainerType(obj)){ - headers["Link"] = "application/ld+json;charset=utf-8;profile=\"http://www.w3.org/ns/anno.jsonld\"" + headers.Link = "application/ld+json;charset=utf-8;profile=\"http://www.w3.org/ns/anno.jsonld\"" } else{ - headers["Link"] = "; rel=\"type\"" + headers.Link = "; rel=\"type\"" } - headers["Allow"] = "GET,OPTIONS,HEAD,PUT,PATCH,DELETE,POST" + headers.Allow = "GET,OPTIONS,HEAD,PUT,PATCH,DELETE,POST" return headers } @@ -154,20 +150,20 @@ const configureLDHeadersFor = function(obj){ //Note that the optimal situation would be to be able to detect the LD-ness of this object //What we have are the arrays returned from the aformentioned getters (/query, /since, /history) //We know we want them to be LD and that they likely contain LD things, but the arrays don't have an @context - let headers = {} + const headers = {} /** if(isLD(obj)){ - headers["Content-Type"] = 'application/ld+json;charset=utf-8;profile="http://www.w3.org/ns/anno.jsonld"' + headers["Content-Type"] = "application/ld+json;charset=utf-8;profile=\"http://www.w3.org/ns/anno.jsonld\"" } else { // This breaks Web Annotation compliance, but allows us to return requested // objects without misrepresenting the content. - headers["Content-Type"] = "application/json;charset=utf-8;" + headers["Content-Type"] = "application/json;charset=utf-8" } */ - headers["Allow"] = "GET,OPTIONS,HEAD,PUT,PATCH,DELETE,POST" - headers["Content-Type"] = 'application/ld+json;charset=utf-8;profile="http://www.w3.org/ns/anno.jsonld"' - headers["Link"] = '; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' + headers.Allow = "GET,OPTIONS,HEAD,PUT,PATCH,DELETE,POST" + headers["Content-Type"] = "application/ld+json;charset=utf-8;profile=\"http://www.w3.org/ns/anno.jsonld\"" + headers.Link = "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\"" return headers } @@ -177,8 +173,7 @@ const configureLDHeadersFor = function(obj){ * return boolean */ const isContainerType = function(obj){ - let answer = false - let typestring = obj["@type"] ?? obj.type ?? "" + const typestring = obj["@type"] ?? obj.type ?? "" const knownContainerTypes = [ "ItemList", "AnnotationPage", @@ -194,11 +189,10 @@ const isContainerType = function(obj){ for(const t of knownContainerTypes){ //Dang those pesky prefixes...circumventing exact match for now if(typestring.includes(t)){ - answer = true - break + return true } } - return answer + return false //return knownContainerTypes.includes(typestring) } @@ -220,15 +214,10 @@ const isLD = function(obj){ */ const configureLastModifiedHeader = function(obj){ let date = "" - if(obj.__rerum){ - if(!obj.__rerum.isOverwritten === ""){ - date = obj.__rerum.isOverwritten - } - else{ - date = obj.__rerum.createdAt - } + if(obj?.__rerum){ + date = obj.__rerum.isOverwritten !== "" ? obj.__rerum.isOverwritten : obj.__rerum.createdAt } - else if(obj.__deleted){ + else if(obj?.__deleted){ date = obj.__deleted.time } //Note that dates like 2021-05-26T10:39:19.328 have been rounded to 2021-05-26T10:39:19 in browser headers. Account for that here.