mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 07:03:37 -05:00
[misc] add a new endpoint for getting deleted docs
This commit is contained in:
parent
c97afede90
commit
8915e1d026
8 changed files with 238 additions and 1 deletions
|
@ -48,6 +48,7 @@ app.param('doc_id', function (req, res, next, docId) {
|
||||||
|
|
||||||
Metrics.injectMetricsRoute(app)
|
Metrics.injectMetricsRoute(app)
|
||||||
|
|
||||||
|
app.get('/project/:project_id/doc-deleted', HttpController.getAllDeletedDocs)
|
||||||
app.get('/project/:project_id/doc', HttpController.getAllDocs)
|
app.get('/project/:project_id/doc', HttpController.getAllDocs)
|
||||||
app.get('/project/:project_id/ranges', HttpController.getAllRanges)
|
app.get('/project/:project_id/ranges', HttpController.getAllRanges)
|
||||||
app.get('/project/:project_id/doc/:doc_id', HttpController.getDoc)
|
app.get('/project/:project_id/doc/:doc_id', HttpController.getDoc)
|
||||||
|
|
|
@ -150,6 +150,10 @@ module.exports = DocManager = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAllDeletedDocs(project_id, filter, callback) {
|
||||||
|
MongoManager.getProjectsDeletedDocs(project_id, filter, callback)
|
||||||
|
},
|
||||||
|
|
||||||
getAllNonDeletedDocs(project_id, filter, callback) {
|
getAllNonDeletedDocs(project_id, filter, callback) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
callback = function (error, docs) {}
|
callback = function (error, docs) {}
|
||||||
|
|
|
@ -95,6 +95,24 @@ module.exports = HttpController = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAllDeletedDocs(req, res, next) {
|
||||||
|
const { project_id } = req.params
|
||||||
|
logger.log({ project_id }, 'getting all deleted docs')
|
||||||
|
DocManager.getAllDeletedDocs(project_id, { name: true }, function (
|
||||||
|
error,
|
||||||
|
docs
|
||||||
|
) {
|
||||||
|
if (error) {
|
||||||
|
return next(error)
|
||||||
|
}
|
||||||
|
res.json(
|
||||||
|
docs.map((doc) => {
|
||||||
|
return { _id: doc._id.toString(), name: doc.name }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
getAllRanges(req, res, next) {
|
getAllRanges(req, res, next) {
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
next = function (error) {}
|
next = function (error) {}
|
||||||
|
|
|
@ -14,6 +14,7 @@ let MongoManager
|
||||||
const { db, ObjectId } = require('./mongodb')
|
const { db, ObjectId } = require('./mongodb')
|
||||||
const logger = require('logger-sharelatex')
|
const logger = require('logger-sharelatex')
|
||||||
const metrics = require('@overleaf/metrics')
|
const metrics = require('@overleaf/metrics')
|
||||||
|
const Settings = require('settings-sharelatex')
|
||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
|
|
||||||
module.exports = MongoManager = {
|
module.exports = MongoManager = {
|
||||||
|
@ -33,6 +34,24 @@ module.exports = MongoManager = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getProjectsDeletedDocs(project_id, filter, callback) {
|
||||||
|
db.docs
|
||||||
|
.find(
|
||||||
|
{
|
||||||
|
project_id: ObjectId(project_id.toString()),
|
||||||
|
deleted: true,
|
||||||
|
// TODO(das7pad): remove name filter after back filling data
|
||||||
|
name: { $exists: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
projection: filter,
|
||||||
|
sort: { deletedAt: -1 },
|
||||||
|
limit: Settings.max_deleted_docs
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toArray(callback)
|
||||||
|
},
|
||||||
|
|
||||||
getProjectsDocs(project_id, options, filter, callback) {
|
getProjectsDocs(project_id, options, filter, callback) {
|
||||||
const query = { project_id: ObjectId(project_id.toString()) }
|
const query = { project_id: ObjectId(project_id.toString()) }
|
||||||
if (!options.include_deleted) {
|
if (!options.include_deleted) {
|
||||||
|
|
|
@ -38,6 +38,8 @@ const Settings = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
max_deleted_docs: parseInt(process.env.MAX_DELETED_DOCS, 10) || 2000,
|
||||||
|
|
||||||
max_doc_length: parseInt(process.env.MAX_DOC_LENGTH) || 2 * 1024 * 1024 // 2mb
|
max_doc_length: parseInt(process.env.MAX_DOC_LENGTH) || 2 * 1024 * 1024 // 2mb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,23 @@ function deleteTestSuite(deleteDoc) {
|
||||||
|
|
||||||
describe('Delete via DELETE', function () {
|
describe('Delete via DELETE', function () {
|
||||||
deleteTestSuite(DocstoreClient.deleteDocLegacy)
|
deleteTestSuite(DocstoreClient.deleteDocLegacy)
|
||||||
|
|
||||||
|
describe('when the doc gets no name on delete', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
DocstoreClient.deleteDocLegacy(this.project_id, this.doc_id, done)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not show the doc in the deleted docs response', function (done) {
|
||||||
|
DocstoreClient.getAllDeletedDocs(
|
||||||
|
this.project_id,
|
||||||
|
(error, deletedDocs) => {
|
||||||
|
if (error) return done(error)
|
||||||
|
expect(deletedDocs).to.deep.equal([])
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Delete via PATCH', function () {
|
describe('Delete via PATCH', function () {
|
||||||
|
@ -292,6 +309,121 @@ describe('Delete via PATCH', function () {
|
||||||
expect(this.res.statusCode).to.equal(400)
|
expect(this.res.statusCode).to.equal(400)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('before deleting anything', function () {
|
||||||
|
it('should show nothing in deleted docs response', function (done) {
|
||||||
|
DocstoreClient.getAllDeletedDocs(
|
||||||
|
this.project_id,
|
||||||
|
(error, deletedDocs) => {
|
||||||
|
if (error) return done(error)
|
||||||
|
expect(deletedDocs).to.deep.equal([])
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when the doc gets a name on delete', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
DocstoreClient.deleteDoc(this.project_id, this.doc_id, done)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show the doc in deleted docs response', function (done) {
|
||||||
|
DocstoreClient.getAllDeletedDocs(
|
||||||
|
this.project_id,
|
||||||
|
(error, deletedDocs) => {
|
||||||
|
if (error) return done(error)
|
||||||
|
expect(deletedDocs).to.deep.equal([
|
||||||
|
{ _id: this.doc_id.toString(), name: 'main.tex' }
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('after deleting multiple docs', function () {
|
||||||
|
beforeEach('create doc2', function (done) {
|
||||||
|
this.doc_id2 = ObjectId()
|
||||||
|
DocstoreClient.createDoc(
|
||||||
|
this.project_id,
|
||||||
|
this.doc_id2,
|
||||||
|
this.lines,
|
||||||
|
this.version,
|
||||||
|
this.ranges,
|
||||||
|
done
|
||||||
|
)
|
||||||
|
})
|
||||||
|
beforeEach('delete doc2', function (done) {
|
||||||
|
DocstoreClient.deleteDocWithName(
|
||||||
|
this.project_id,
|
||||||
|
this.doc_id2,
|
||||||
|
'two.tex',
|
||||||
|
done
|
||||||
|
)
|
||||||
|
})
|
||||||
|
beforeEach('create doc3', function (done) {
|
||||||
|
this.doc_id3 = ObjectId()
|
||||||
|
DocstoreClient.createDoc(
|
||||||
|
this.project_id,
|
||||||
|
this.doc_id3,
|
||||||
|
this.lines,
|
||||||
|
this.version,
|
||||||
|
this.ranges,
|
||||||
|
done
|
||||||
|
)
|
||||||
|
})
|
||||||
|
beforeEach('delete doc3', function (done) {
|
||||||
|
DocstoreClient.deleteDocWithName(
|
||||||
|
this.project_id,
|
||||||
|
this.doc_id3,
|
||||||
|
'three.tex',
|
||||||
|
done
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('should show all the docs as deleted', function (done) {
|
||||||
|
DocstoreClient.getAllDeletedDocs(
|
||||||
|
this.project_id,
|
||||||
|
(error, deletedDocs) => {
|
||||||
|
if (error) return done(error)
|
||||||
|
|
||||||
|
expect(deletedDocs).to.deep.equal([
|
||||||
|
{ _id: this.doc_id3.toString(), name: 'three.tex' },
|
||||||
|
{ _id: this.doc_id2.toString(), name: 'two.tex' },
|
||||||
|
{ _id: this.doc_id.toString(), name: 'main.tex' }
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with one more than max_deleted_docs permits', function () {
|
||||||
|
let maxDeletedDocsBefore
|
||||||
|
beforeEach(function () {
|
||||||
|
maxDeletedDocsBefore = Settings.max_deleted_docs
|
||||||
|
Settings.max_deleted_docs = 2
|
||||||
|
})
|
||||||
|
afterEach(function () {
|
||||||
|
Settings.max_deleted_docs = maxDeletedDocsBefore
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should omit the first deleted doc', function (done) {
|
||||||
|
DocstoreClient.getAllDeletedDocs(
|
||||||
|
this.project_id,
|
||||||
|
(error, deletedDocs) => {
|
||||||
|
if (error) return done(error)
|
||||||
|
|
||||||
|
expect(deletedDocs).to.deep.equal([
|
||||||
|
{ _id: this.doc_id3.toString(), name: 'three.tex' },
|
||||||
|
{ _id: this.doc_id2.toString(), name: 'two.tex' }
|
||||||
|
// dropped main.tex
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Destroying a project's documents", function () {
|
describe("Destroying a project's documents", function () {
|
||||||
|
|
|
@ -85,6 +85,22 @@ module.exports = DocstoreClient = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAllDeletedDocs(project_id, callback) {
|
||||||
|
request.get(
|
||||||
|
{
|
||||||
|
url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/doc-deleted`,
|
||||||
|
json: true
|
||||||
|
},
|
||||||
|
(error, res, body) => {
|
||||||
|
if (error) return callback(error)
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
return callback(new Error('unexpected statusCode'))
|
||||||
|
}
|
||||||
|
callback(null, body)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
getAllRanges(project_id, callback) {
|
getAllRanges(project_id, callback) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
callback = function (error, res, body) {}
|
callback = function (error, res, body) {}
|
||||||
|
|
|
@ -28,7 +28,8 @@ describe('MongoManager', function () {
|
||||||
ObjectId
|
ObjectId
|
||||||
},
|
},
|
||||||
'@overleaf/metrics': { timeAsyncMethod: sinon.stub() },
|
'@overleaf/metrics': { timeAsyncMethod: sinon.stub() },
|
||||||
'logger-sharelatex': { log() {} }
|
'logger-sharelatex': { log() {} },
|
||||||
|
'settings-sharelatex': { max_deleted_docs: 42 }
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
console
|
console
|
||||||
|
@ -175,6 +176,50 @@ describe('MongoManager', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('getProjectsDeletedDocs', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
this.filter = { name: true }
|
||||||
|
this.doc1 = { _id: '1', name: 'mock-doc1.tex' }
|
||||||
|
this.doc2 = { _id: '2', name: 'mock-doc2.tex' }
|
||||||
|
this.doc3 = { _id: '3', name: 'mock-doc3.tex' }
|
||||||
|
this.db.docs.find = sinon.stub().returns({
|
||||||
|
toArray: sinon.stub().yields(null, [this.doc1, this.doc2, this.doc3])
|
||||||
|
})
|
||||||
|
this.callback.callsFake(done)
|
||||||
|
this.MongoManager.getProjectsDeletedDocs(
|
||||||
|
this.project_id,
|
||||||
|
this.filter,
|
||||||
|
this.callback
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should find the deleted docs via the project_id', function () {
|
||||||
|
this.db.docs.find
|
||||||
|
.calledWith({
|
||||||
|
project_id: ObjectId(this.project_id),
|
||||||
|
deleted: true,
|
||||||
|
name: { $exists: true }
|
||||||
|
})
|
||||||
|
.should.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should filter, sort by deletedAt and limit', function () {
|
||||||
|
this.db.docs.find
|
||||||
|
.calledWith(sinon.match.any, {
|
||||||
|
projection: this.filter,
|
||||||
|
sort: { deletedAt: -1 },
|
||||||
|
limit: 42
|
||||||
|
})
|
||||||
|
.should.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call the callback with the docs', function () {
|
||||||
|
this.callback
|
||||||
|
.calledWith(null, [this.doc1, this.doc2, this.doc3])
|
||||||
|
.should.equal(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('upsertIntoDocCollection', function () {
|
describe('upsertIntoDocCollection', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.db.docs.updateOne = sinon.stub().callsArgWith(3, this.stubbedErr)
|
this.db.docs.updateOne = sinon.stub().callsArgWith(3, this.stubbedErr)
|
||||||
|
|
Loading…
Reference in a new issue