Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2cb5d68
feat: add document versioning (WIP)
eduardoboucas Jan 7, 2019
5ebab69
refactor: temporarily disable versioning on DELETE
eduardoboucas Jan 7, 2019
92ddbfd
test: add tests for document versioning (WIP)
eduardoboucas Jan 7, 2019
6416846
test: add acceptance test for rolling back docs with Reference fields
eduardoboucas Jan 8, 2019
d459359
feat: add improved document versioning
eduardoboucas Jan 23, 2019
5b356e2
refactor: rebuild document versioning without diffs
eduardoboucas Feb 1, 2019
21e401d
test: adjust tests for new document versioning
eduardoboucas Feb 1, 2019
c3fbc06
feat: add document versioning (WIP)
eduardoboucas Jan 7, 2019
4a29be2
refactor: temporarily disable versioning on DELETE
eduardoboucas Jan 7, 2019
51602d4
test: add tests for document versioning (WIP)
eduardoboucas Jan 7, 2019
bf0f2e1
test: add acceptance test for rolling back docs with Reference fields
eduardoboucas Jan 8, 2019
ece6d81
feat: add improved document versioning
eduardoboucas Jan 23, 2019
0102012
refactor: rebuild document versioning without diffs
eduardoboucas Feb 1, 2019
9d0d7a6
test: adjust tests for new document versioning
eduardoboucas Feb 1, 2019
cdd9ef0
test: fix document history tests
eduardoboucas Feb 1, 2019
32eac02
Merge branch 'feature/document-versioning' of github.com:dadi/api int…
eduardoboucas Feb 1, 2019
77e6dd6
chore: make Jim happy
eduardoboucas Feb 1, 2019
2d5ec94
feat: add feature code for Versioning API
eduardoboucas Feb 1, 2019
16f2616
refactor: use enableVersioning and versioningCollection properties
eduardoboucas Feb 4, 2019
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
28 changes: 25 additions & 3 deletions dadi/lib/controller/documents.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Collection.prototype.delete = function (req, res, next) {

this.model.delete({
client: req.dadiApiClient,
description: req.body && req.body.description,
query,
req
}).then(({deletedCount, totalCount}) => {
Expand Down Expand Up @@ -104,7 +105,8 @@ Collection.prototype.get = function (req, res, next) {
language: options.lang,
query,
options: queryOptions,
req
req,
version: req.params.id && options.version
}).then(results => {
return done(null, results)
}).catch(error => {
Expand Down Expand Up @@ -135,13 +137,15 @@ Collection.prototype.post = function (req, res, next) {
if (req.params.id || req.body.update) {
internals._lastModifiedBy = req.dadiApiClient && req.dadiApiClient.clientId

let description
let query = {}
let update = {}

if (req.params.id) {
query._id = req.params.id
update = req.body
} else {
description = req.body.description
query = req.body.query
update = req.body.update
}
Expand All @@ -154,6 +158,7 @@ Collection.prototype.post = function (req, res, next) {
return this.model.update({
client: req.dadiApiClient,
compose: options.compose,
description,
internals,
query,
req,
Expand Down Expand Up @@ -214,7 +219,7 @@ Collection.prototype.registerRoutes = function (route, filePath) {
})

// Creating generic route.
this.server.app.use(`${route}/:id(${this.ID_PATTERN})?/:action(count|search|stats)?`, (req, res, next) => {
this.server.app.use(`${route}/:id(${this.ID_PATTERN})?/:action(count|search|stats|versions)?`, (req, res, next) => {
try {
// Map request method to controller method.
let method = req.params.action || (req.method && req.method.toLowerCase())
Expand Down Expand Up @@ -252,7 +257,24 @@ Collection.prototype.stats = function (req, res, next) {

Collection.prototype.unregisterRoutes = function (route) {
this.server.app.unuse(`${route}/config`)
this.server.app.unuse(`${route}/:id(${this.ID_PATTERN})?/:action(count|search|stats)?`)
this.server.app.unuse(`${route}/:id(${this.ID_PATTERN})?/:action(count|search|stats|versions)?`)
}

Collection.prototype.versions = function (req, res, next) {
let method = req.method && req.method.toLowerCase()

if (method !== 'get') {
return next()
}

this.model.getVersions({
client: req.dadiApiClient,
documentId: req.params.id
}).then(response => {
return help.sendBackJSON(200, res, next)(null, response)
}).catch(error => {
return help.sendBackJSON(null, res, next)(error)
})
}

module.exports = function (model, server) {
Expand Down
9 changes: 0 additions & 9 deletions dadi/lib/controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,6 @@ Controller.prototype._prepareQueryOptions = function (options) {
queryOptions.compose = options.compose
}

// History.
if (options.includeHistory) {
queryOptions.includeHistory = options.includeHistory === 'true'

if (options.historyFilters) {
queryOptions.historyFilters = options.historyFilters
}
}

// sorting
let sort = {}
let sortOptions = help.isJSON(options.sort)
Expand Down
8 changes: 0 additions & 8 deletions dadi/lib/model/collections/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,6 @@ function create ({
Object.assign(document, internals)
}

// Add placeholder for document history.
if (this.history) {
document._history = []
}

// Add initial revision number.
document._version = 1

return Object.keys(document).reduce((documentTransform, field) => {
if (field === '_id') {
return documentTransform
Expand Down
14 changes: 7 additions & 7 deletions dadi/lib/model/collections/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ const logger = require('@dadi/logger')
* Deletes documents from the database.
*
* @param {Object} client - client to check permissions for
* @param {String} description - optional update description
* @param {Object} query - query to find documents to delete
* @param {Object} req - request to be passed to hooks
* @param {Boolean} validate - whether to run validation
* @return {Promise<DeleteResult>}
*/
function deleteFn ({
client,
description,
query,
req,
validate = true
Expand Down Expand Up @@ -103,13 +105,11 @@ function deleteFn ({
return Promise.reject(error)
}

// Create a revision for each of the documents about to be deleted.
if (this.history && deletedDocuments.length > 0) {
return this.history.createEach(
deletedDocuments,
'delete',
this
)
// Create a revision for each of the updated documents.
if (this.history) {
return this.history.addVersion(deletedDocuments, {
description
})
}
}).then(() => {
// Run any `beforeDelete` hooks.
Expand Down
36 changes: 18 additions & 18 deletions dadi/lib/model/collections/find.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@ const debug = require('debug')('api:model')
/**
* Finds documents in the database.
*
* @param {Object} client - client to check permissions for
* @param {String} language - ISO code for the language to translate documents to
* @param {Object} query - query to match documents against
* @param {Object} options
* @param {Object} client - client to check permissions for
* @param {Boolean} isRestIDQuery - whether the query targets a specific document by ID
* @param {String} language - ISO code for the language to translate documents to
* @param {Object} query - query to match documents against
* @param {Object} options
* @param {Number} version - version of the document to retrieve
* @return {Promise<ResultSet>}
*/
function find ({
client,
isRestIDQuery,
language,
query = {},
options = {}
options = {},
version
} = {}) {
if (!this.connection.db) {
return Promise.reject(
Expand Down Expand Up @@ -120,27 +124,23 @@ function find ({

queryFields = fields

const queryOptions = Object.assign({}, options, {
fields: queryFields
})

if (isRestIDQuery && version && this.history) {
return this.history.getVersion(version, queryOptions)
}

return this._transformQuery(query, options).then(query => {
return this.connection.db.find({
query,
collection: this.name,
options: Object.assign({}, options, {
compose: undefined,
fields: queryFields,
historyFilters: undefined
}),
options: queryOptions,
schema: this.schema,
settings: this.settings
})
})
}).then(response => {
if (options.includeHistory) {
options.includeHistory = undefined

return this._injectHistory(response, options)
}

return response
})
}

Expand Down
8 changes: 6 additions & 2 deletions dadi/lib/model/collections/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const logger = require('@dadi/logger')
* @param {Object} options
* @param {Boolean} rawOutput - whether to bypass formatting routine
* @param {Object} req - request object to pass to hooks
* @param {Number} version - version of the document to retrieve
* @return {Promise<ResultSet>}
*/
function get ({
Expand All @@ -38,7 +39,8 @@ function get ({
query = {},
options = {},
rawOutput = false,
req
req,
version
}) {
// Is this a RESTful query by ID?
let isRestIDQuery = req && req.params && req.params.id
Expand Down Expand Up @@ -73,9 +75,11 @@ function get ({
}).then(query => {
return this.find({
client,
isRestIDQuery,
language,
query,
options
options,
version
})
}).then(({metadata, results}) => {
if (isRestIDQuery && results.length === 0) {
Expand Down
63 changes: 63 additions & 0 deletions dadi/lib/model/collections/getVersions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Returns stats relating to the collection.
*
* @param {Object} client - client to check permissions for
* @param {String} documentId - ID of the object to get versions for
* @return {Promise<Stats>}
*/
function getVersions ({
client,
documentId
} = {}) {
if (!this.connection.db) {
return Promise.reject(
new Error('DB_DISCONNECTED')
)
}

let response = {
results: []
}

return this.validateAccess({
client,
type: 'read'
}).then(() => {
if (!this.history) {
const error = new Error('History not enabled for collection')

error.statusCode = 404

return Promise.reject(error)
}

return this.history.getVersions(documentId).then(({results}) => {
if (results.length === 0) {
return this.count({
client,
query: {
_id: documentId
}
}).then(({metadata}) => {
if (metadata.totalCount === 0) {
const error = new Error('Document not found')

error.statusCode = 404

return Promise.reject(error)
}

return results
})
}

return results
}).then(results => {
response.results = results

return response
})
})
}

module.exports = getVersions
27 changes: 14 additions & 13 deletions dadi/lib/model/collections/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const logger = require('@dadi/logger')
* @param {Object} client - client to check permissions for
* @param {Boolean|Number} compose - the composition settings for the result
* @param {Object} query - query to match documents against
* @param {String} description - optional update description
* @param {Object} update - properties to update documents with
* @param {Object} internals - internal properties to inject in documents
* @param {Boolean} rawOutput - whether to bypass output formatting
Expand All @@ -46,12 +47,13 @@ const logger = require('@dadi/logger')
function update ({
client,
compose = true,
query = {},
update,
description,
internals = {},
query = {},
rawOutput = false,
removeInternalProperties = true,
req,
update,
validate = true
}) {
debug(
Expand Down Expand Up @@ -200,8 +202,7 @@ function update ({
query,
schema: this.schema,
update: {
$set: update,
$inc: { _version: 1 }
$set: update
}
})
}).then(({matchedCount}) => {
Expand All @@ -212,15 +213,6 @@ function update ({

return Promise.reject(error)
}

// Create a revision for each of the updated documents.
if (this.history) {
return this.history.createEach(
updatedDocuments,
'update',
this
)
}
}).then(() => {
let updatedDocumentsQuery = {
_id: {
Expand Down Expand Up @@ -266,6 +258,15 @@ function update ({

return data
})
}).then(response => {
// Create a revision for each of the updated documents.
if (this.history && updatedDocuments.length > 0) {
return this.history.addVersion(updatedDocuments, {
description
}).then(historyResponse => response)
}

return response
}).catch(error => {
// Dealing with the case of an impossible query. We can simply return
// an empty result set here.
Expand Down
Loading