-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathdelete.js
More file actions
223 lines (218 loc) · 9.21 KB
/
delete.js
File metadata and controls
223 lines (218 loc) · 9.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#!/usr/bin/env node
/**
* Delete operations for RERUM v1
* @author Claude Sonnet 4, cubap, thehabes
*/
import { newID, isValidID, db } from '../database/index.js'
import utils from '../utils.js'
import { createExpressError, getAgentClaim, parseDocumentID, getAllVersions, getAllDescendants } from './utils.js'
/**
* Mark an object as deleted in the database.
* Support /v1/delete/{id}. Note this is not v1/api/delete, that is not possible (XHR does not support DELETE with body)
* Note /v1/delete/{blank} does not route here. It routes to the generic 404.
* Respond RESTfully
*
* The user may be trying to call /delete and pass in the obj in the body. XHR does not support bodies in delete.
* If there is no id parameter, this is a 400
*
* If there is an id parameter, we ignore body, and continue with that id
*
* */
const deleteObj = async function(req, res, next) {
let id
let err = { message: `` }
try {
id = req.params["_id"] ?? parseDocumentID(JSON.parse(JSON.stringify(req.body))["@id"]) ?? parseDocumentID(JSON.parse(JSON.stringify(req.body))["id"])
} catch(error){
next(createExpressError(error))
return
}
let agentRequestingDelete = getAgentClaim(req, next)
let originalObject
try {
originalObject = await db.findOne({"$or":[{"_id": id}, {"__rerum.slug": id}]})
} catch (error) {
next(createExpressError(error))
return
}
if (null !== originalObject) {
let safe_original = JSON.parse(JSON.stringify(originalObject))
if (utils.isDeleted(safe_original)) {
err = Object.assign(err, {
message: `The object you are trying to delete is already deleted. ${err.message}`,
status: 403
})
}
else if (utils.isReleased(safe_original)) {
err = Object.assign(err, {
message: `The object you are trying to delete is released. Fork to make changes. ${err.message}`,
status: 403
})
}
else if (!utils.isGenerator(safe_original, agentRequestingDelete)) {
err = Object.assign(err, {
message: `You are not the generating agent for this object and so are not authorized to delete it. ${err.message}`,
status: 401
})
}
if (err.status) {
next(createExpressError(err))
return
}
let preserveID = safe_original["@id"]
let deletedFlag = {} //The __deleted flag is a JSONObject
deletedFlag["object"] = JSON.parse(JSON.stringify(originalObject))
deletedFlag["deletor"] = agentRequestingDelete
deletedFlag["time"] = new Date(Date.now()).toISOString().replace("Z", "")
let deletedObject = {
"@id": preserveID,
"__deleted": deletedFlag,
"_id": id
}
if (await healHistoryTree(safe_original)) {
let result
try {
result = await db.replaceOne({ "_id": originalObject["_id"] }, deletedObject)
} catch (error) {
next(createExpressError(error))
return
}
if (result.modifiedCount === 0) {
//result didn't error out, the action was not performed. Sometimes, this is a neutral thing. Sometimes it is indicative of an error.
err.message = "The original object was not replaced with the deleted object in the database."
err.status = 500
next(createExpressError(err))
return
}
//204 to say it is deleted and there is nothing in the body
console.log("Object deleted: " + preserveID)
res.sendStatus(204)
return
}
//Not sure we can get here, as healHistoryTree might throw and error.
err.message = "The history tree for the object being deleted could not be mended."
err.status = 500
next(createExpressError(err))
return
}
err.message = "No object with this id could be found in RERUM. Cannot delete."
err.status = 404
next(createExpressError(err))
}
/**
* An internal method to handle when an object is deleted and the history tree around it will need amending.
* This function should only be handed a reliable object from mongo.
*
* @param obj A JSONObject of the object being deleted.
* @return A boolean representing whether or not this function succeeded.
*/
async function healHistoryTree(obj) {
let previous_id = ""
let prime_id = ""
let next_ids = []
if (obj["__rerum"]) {
previous_id = obj["__rerum"]["history"]["previous"]
prime_id = obj["__rerum"]["history"]["prime"]
next_ids = obj["__rerum"]["history"]["next"]
}
else {
console.error("This object has no history because it has no '__rerum' property. There is nothing to heal.")
return false
}
let objToDeleteisRoot = (prime_id === "root")
//Update the history.previous of all the next ids in the array of the deleted object
try {
for (const nextID of next_ids) {
let objWithUpdate = {}
const nextIdForQuery = parseDocumentID(nextID)
const objToUpdate = await db.findOne({"$or":[{"_id": nextIdForQuery}, {"__rerum.slug": nextIdForQuery}]})
if (null !== objToUpdate) {
let fixHistory = JSON.parse(JSON.stringify(objToUpdate))
if (objToDeleteisRoot) {
//This means this next object must become root.
//Strictly, all history trees must have num(root) > 0.
if (await newTreePrime(fixHistory)) {
fixHistory["__rerum"]["history"]["prime"] = "root"
//The previous always inherited in this case, even if it isn't there.
fixHistory["__rerum"]["history"]["previous"] = previous_id
}
else {
throw Error("Could not update all descendants with their new prime value")
}
}
else if (previous_id !== "") {
//The object being deleted had a previous. That is now absorbed by this next object to mend the gap.
fixHistory["__rerum"]["history"]["previous"] = previous_id
}
else {
throw Error("object did not have previous and was not root.")
}
let verify = await db.replaceOne({ "_id": objToUpdate["_id"] }, fixHistory)
if (verify.modifiedCount === 0) {
throw Error("Could not update all descendants with their new prime value")
}
}
else {
throw Error("Could not update all descendants with their new prime value")
}
}
if (previous_id.indexOf(process.env.RERUM_PREFIX) > -1) {
let previousIdForQuery = parseDocumentID(previous_id)
const objToUpdate2 = await db.findOne({"$or":[{"_id": previousIdForQuery}, {"__rerum.slug": previousIdForQuery}]})
if (null !== objToUpdate2) {
let fixHistory2 = JSON.parse(JSON.stringify(objToUpdate2))
let origNextArray = fixHistory2["__rerum"]["history"]["next"]
let newNextArray = [...origNextArray]
newNextArray = newNextArray.filter(id => id !== obj["@id"])
newNextArray = [...newNextArray, ...next_ids]
fixHistory2["__rerum"]["history"]["next"] = newNextArray
let verify2 = await db.replaceOne({ "_id": objToUpdate2["_id"] }, fixHistory2)
if (verify2.modifiedCount === 0) {
throw Error("Could not update all ancestors with their altered next value")
}
}
else {
throw Error("Could not update all ancestors with their altered next value: cannot find ancestor.")
}
}
} catch (error) {
console.error(error)
return false
}
return true
}
/**
* An internal method to make all descendants of this JSONObject take on a new history.prime = this object's @id
* This should only be fed a reliable object from mongo
* @param obj A new prime object whose descendants must take on its id
*/
async function newTreePrime(obj) {
if (obj["@id"]) {
let primeID = obj["@id"]
let ls_versions = []
let descendants = []
try {
ls_versions = await getAllVersions(obj)
descendants = getAllDescendants(ls_versions, obj, [])
} catch (error) {
// fail silently
}
for (const d of descendants) {
let objWithUpdate = JSON.parse(JSON.stringify(d))
objWithUpdate["__rerum"]["history"]["prime"] = primeID
let result = await db.replaceOne({ "_id": d["_id"] }, objWithUpdate)
if (result.modifiedCount === 0) {
console.error("Could not update all descendants with their new prime value: newTreePrime failed")
return false
}
}
}
else {
console.error("newTreePrime failed. Obj did not have '@id'.")
return false
}
return true
}
export {
deleteObj
}