Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 42 additions & 34 deletions controllers/bulk.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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}]})
Expand All @@ -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"])
Expand All @@ -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
Expand Down
42 changes: 20 additions & 22 deletions controllers/crud.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,18 @@
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()
Expand All @@ -40,16 +38,16 @@
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
Expand All @@ -64,12 +62,12 @@
* */
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
}
Expand All @@ -79,7 +77,7 @@
try {
let matches = await db.find(props).limit(limit).skip(skip).toArray()
matches = matches.map(o => idNegotiation(o))
res.set(utils.configureLDHeadersFor(matches))

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query object depends on a
user-provided value
.
res.json(matches)
} catch (error) {
next(createExpressError(error))
Expand All @@ -93,9 +91,9 @@
* */
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
Expand All @@ -105,12 +103,12 @@
// 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
}
Expand Down
15 changes: 7 additions & 8 deletions controllers/gog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
22 changes: 10 additions & 12 deletions controllers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading
Loading