mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
342 lines
No EOL
12 KiB
JavaScript
342 lines
No EOL
12 KiB
JavaScript
/* eslint-disable
|
|
camelcase,
|
|
handle-callback-err,
|
|
*/
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
// Fix any style issues and re-enable lint.
|
|
/*
|
|
* decaffeinate suggestions:
|
|
* DS101: Remove unnecessary use of Array.from
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
* DS103: Rewrite code to no longer use __guard__
|
|
* DS207: Consider shorter variations of null checks
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
*/
|
|
let HttpController;
|
|
const DocumentManager = require("./DocumentManager");
|
|
const HistoryManager = require("./HistoryManager");
|
|
const ProjectManager = require("./ProjectManager");
|
|
const Errors = require("./Errors");
|
|
const logger = require("logger-sharelatex");
|
|
const Metrics = require("./Metrics");
|
|
const ProjectFlusher = require("./ProjectFlusher");
|
|
const DeleteQueueManager = require("./DeleteQueueManager");
|
|
const async = require("async");
|
|
|
|
const TWO_MEGABYTES = 2 * 1024 * 1024;
|
|
|
|
module.exports = (HttpController = {
|
|
getDoc(req, res, next) {
|
|
let fromVersion;
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
doc_id
|
|
} = req.params;
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
logger.log({project_id, doc_id}, "getting doc via http");
|
|
const timer = new Metrics.Timer("http.getDoc");
|
|
|
|
if ((req.query != null ? req.query.fromVersion : undefined) != null) {
|
|
fromVersion = parseInt(req.query.fromVersion, 10);
|
|
} else {
|
|
fromVersion = -1;
|
|
}
|
|
|
|
return DocumentManager.getDocAndRecentOpsWithLock(project_id, doc_id, fromVersion, function(error, lines, version, ops, ranges, pathname) {
|
|
timer.done();
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id, doc_id}, "got doc via http");
|
|
if ((lines == null) || (version == null)) {
|
|
return next(new Errors.NotFoundError("document not found"));
|
|
}
|
|
return res.json({
|
|
id: doc_id,
|
|
lines,
|
|
version,
|
|
ops,
|
|
ranges,
|
|
pathname
|
|
});
|
|
});
|
|
},
|
|
|
|
_getTotalSizeOfLines(lines) {
|
|
let size = 0;
|
|
for (const line of Array.from(lines)) {
|
|
size += (line.length + 1);
|
|
}
|
|
return size;
|
|
},
|
|
|
|
getProjectDocsAndFlushIfOld(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
const projectStateHash = req.query != null ? req.query.state : undefined;
|
|
// exclude is string of existing docs "id:version,id:version,..."
|
|
const excludeItems = __guard__(req.query != null ? req.query.exclude : undefined, x => x.split(',')) || [];
|
|
logger.log({project_id, exclude: excludeItems}, "getting docs via http");
|
|
const timer = new Metrics.Timer("http.getAllDocs");
|
|
const excludeVersions = {};
|
|
for (const item of Array.from(excludeItems)) {
|
|
const [id,version] = Array.from(item != null ? item.split(':') : undefined);
|
|
excludeVersions[id] = version;
|
|
}
|
|
logger.log({project_id, projectStateHash, excludeVersions}, "excluding versions");
|
|
return ProjectManager.getProjectDocsAndFlushIfOld(project_id, projectStateHash, excludeVersions, function(error, result) {
|
|
timer.done();
|
|
if (error instanceof Errors.ProjectStateChangedError) {
|
|
return res.sendStatus(409); // conflict
|
|
} else if (error != null) {
|
|
return next(error);
|
|
} else {
|
|
logger.log({project_id, result: ((Array.from(result).map((doc) => `${doc._id}:${doc.v}`)))}, "got docs via http");
|
|
return res.send(result);
|
|
}
|
|
});
|
|
},
|
|
|
|
clearProjectState(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
const timer = new Metrics.Timer("http.clearProjectState");
|
|
logger.log({project_id}, "clearing project state via http");
|
|
return ProjectManager.clearProjectState(project_id, function(error) {
|
|
timer.done();
|
|
if (error != null) {
|
|
return next(error);
|
|
} else {
|
|
return res.sendStatus(200);
|
|
}
|
|
});
|
|
},
|
|
|
|
setDoc(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
doc_id
|
|
} = req.params;
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
const {lines, source, user_id, undoing} = req.body;
|
|
const lineSize = HttpController._getTotalSizeOfLines(lines);
|
|
if (lineSize > TWO_MEGABYTES) {
|
|
logger.log({project_id, doc_id, source, lineSize, user_id}, "document too large, returning 406 response");
|
|
return res.sendStatus(406);
|
|
}
|
|
logger.log({project_id, doc_id, lines, source, user_id, undoing}, "setting doc via http");
|
|
const timer = new Metrics.Timer("http.setDoc");
|
|
return DocumentManager.setDocWithLock(project_id, doc_id, lines, source, user_id, undoing, function(error) {
|
|
timer.done();
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id, doc_id}, "set doc via http");
|
|
return res.sendStatus(204);
|
|
});
|
|
}, // No Content
|
|
|
|
|
|
flushDocIfLoaded(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
doc_id
|
|
} = req.params;
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
logger.log({project_id, doc_id}, "flushing doc via http");
|
|
const timer = new Metrics.Timer("http.flushDoc");
|
|
return DocumentManager.flushDocIfLoadedWithLock(project_id, doc_id, function(error) {
|
|
timer.done();
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id, doc_id}, "flushed doc via http");
|
|
return res.sendStatus(204);
|
|
});
|
|
}, // No Content
|
|
|
|
deleteDoc(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
doc_id
|
|
} = req.params;
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
const ignoreFlushErrors = req.query.ignore_flush_errors === 'true';
|
|
const timer = new Metrics.Timer("http.deleteDoc");
|
|
logger.log({project_id, doc_id}, "deleting doc via http");
|
|
return DocumentManager.flushAndDeleteDocWithLock(project_id, doc_id, { ignoreFlushErrors }, function(error) {
|
|
timer.done();
|
|
// There is no harm in flushing project history if the previous call
|
|
// failed and sometimes it is required
|
|
HistoryManager.flushProjectChangesAsync(project_id);
|
|
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id, doc_id}, "deleted doc via http");
|
|
return res.sendStatus(204);
|
|
});
|
|
}, // No Content
|
|
|
|
flushProject(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
logger.log({project_id}, "flushing project via http");
|
|
const timer = new Metrics.Timer("http.flushProject");
|
|
return ProjectManager.flushProjectWithLocks(project_id, function(error) {
|
|
timer.done();
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id}, "flushed project via http");
|
|
return res.sendStatus(204);
|
|
});
|
|
}, // No Content
|
|
|
|
deleteProject(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
logger.log({project_id}, "deleting project via http");
|
|
const options = {};
|
|
if (req.query != null ? req.query.background : undefined) { options.background = true; } // allow non-urgent flushes to be queued
|
|
if (req.query != null ? req.query.shutdown : undefined) { options.skip_history_flush = true; } // don't flush history when realtime shuts down
|
|
if (req.query != null ? req.query.background : undefined) {
|
|
return ProjectManager.queueFlushAndDeleteProject(project_id, function(error) {
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id}, "queue delete of project via http");
|
|
return res.sendStatus(204);
|
|
}); // No Content
|
|
} else {
|
|
const timer = new Metrics.Timer("http.deleteProject");
|
|
return ProjectManager.flushAndDeleteProjectWithLocks(project_id, options, function(error) {
|
|
timer.done();
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id}, "deleted project via http");
|
|
return res.sendStatus(204);
|
|
});
|
|
}
|
|
}, // No Content
|
|
|
|
deleteMultipleProjects(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const project_ids = (req.body != null ? req.body.project_ids : undefined) || [];
|
|
logger.log({project_ids}, "deleting multiple projects via http");
|
|
return async.eachSeries(project_ids, function(project_id, cb) {
|
|
logger.log({project_id}, "queue delete of project via http");
|
|
return ProjectManager.queueFlushAndDeleteProject(project_id, cb);
|
|
}
|
|
, function(error) {
|
|
if (error != null) { return next(error); }
|
|
return res.sendStatus(204);
|
|
});
|
|
}, // No Content
|
|
|
|
acceptChanges(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {project_id, doc_id} = req.params;
|
|
let change_ids = req.body != null ? req.body.change_ids : undefined;
|
|
if ((change_ids == null)) {
|
|
change_ids = [ req.params.change_id ];
|
|
}
|
|
logger.log({project_id, doc_id}, `accepting ${ change_ids.length } changes via http`);
|
|
const timer = new Metrics.Timer("http.acceptChanges");
|
|
return DocumentManager.acceptChangesWithLock(project_id, doc_id, change_ids, function(error) {
|
|
timer.done();
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id, doc_id}, `accepted ${ change_ids.length } changes via http`);
|
|
return res.sendStatus(204);
|
|
});
|
|
}, // No Content
|
|
|
|
deleteComment(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {project_id, doc_id, comment_id} = req.params;
|
|
logger.log({project_id, doc_id, comment_id}, "deleting comment via http");
|
|
const timer = new Metrics.Timer("http.deleteComment");
|
|
return DocumentManager.deleteCommentWithLock(project_id, doc_id, comment_id, function(error) {
|
|
timer.done();
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id, doc_id, comment_id}, "deleted comment via http");
|
|
return res.sendStatus(204);
|
|
});
|
|
}, // No Content
|
|
|
|
updateProject(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const timer = new Metrics.Timer("http.updateProject");
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
const {projectHistoryId, userId, docUpdates, fileUpdates, version} = req.body;
|
|
logger.log({project_id, docUpdates, fileUpdates, version}, "updating project via http");
|
|
|
|
return ProjectManager.updateProjectWithLocks(project_id, projectHistoryId, userId, docUpdates, fileUpdates, version, function(error) {
|
|
timer.done();
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id}, "updated project via http");
|
|
return res.sendStatus(204);
|
|
});
|
|
}, // No Content
|
|
|
|
resyncProjectHistory(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
const {
|
|
project_id
|
|
} = req.params;
|
|
const {projectHistoryId, docs, files} = req.body;
|
|
|
|
logger.log({project_id, docs, files}, "queuing project history resync via http");
|
|
return HistoryManager.resyncProjectHistory(project_id, projectHistoryId, docs, files, function(error) {
|
|
if (error != null) { return next(error); }
|
|
logger.log({project_id}, "queued project history resync via http");
|
|
return res.sendStatus(204);
|
|
});
|
|
},
|
|
|
|
flushAllProjects(req, res, next ){
|
|
if (next == null) { next = function(error){}; }
|
|
res.setTimeout(5 * 60 * 1000);
|
|
const options = {
|
|
limit : req.query.limit || 1000,
|
|
concurrency : req.query.concurrency || 5,
|
|
dryRun : req.query.dryRun || false
|
|
};
|
|
return ProjectFlusher.flushAllProjects(options, function(err, project_ids){
|
|
if (err != null) {
|
|
logger.err({err}, "error bulk flushing projects");
|
|
return res.sendStatus(500);
|
|
} else {
|
|
return res.send(project_ids);
|
|
}
|
|
});
|
|
},
|
|
|
|
flushQueuedProjects(req, res, next) {
|
|
if (next == null) { next = function(error) {}; }
|
|
res.setTimeout(10 * 60 * 1000);
|
|
const options = {
|
|
limit : req.query.limit || 1000,
|
|
timeout: 5 * 60 * 1000,
|
|
min_delete_age: req.query.min_delete_age || (5 * 60 * 1000)
|
|
};
|
|
return DeleteQueueManager.flushAndDeleteOldProjects(options, function(err, flushed){
|
|
if (err != null) {
|
|
logger.err({err}, "error flushing old projects");
|
|
return res.sendStatus(500);
|
|
} else {
|
|
logger.log({flushed}, "flush of queued projects completed");
|
|
return res.send({flushed});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
function __guard__(value, transform) {
|
|
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
|
|
} |