mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #24775 from overleaf/em-bypass-project-history
Call history-v1 directly for latest history and changes GitOrigin-RevId: 39c32dd50ff7875f82bbb2716da753a9c3e6e81d
This commit is contained in:
@@ -457,6 +457,19 @@ async function _pipeHistoryZipToResponse(v1ProjectId, version, name, req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getLatestHistory(req, res, next) {
|
||||
const projectId = req.params.project_id
|
||||
const history = await HistoryManager.promises.getLatestHistory(projectId)
|
||||
res.json(history)
|
||||
}
|
||||
|
||||
async function getChanges(req, res, next) {
|
||||
const projectId = req.params.project_id
|
||||
const since = req.query.since
|
||||
const changes = await HistoryManager.promises.getChanges(projectId, { since })
|
||||
res.json(changes)
|
||||
}
|
||||
|
||||
function isPrematureClose(err) {
|
||||
return (
|
||||
err instanceof Error &&
|
||||
@@ -480,6 +493,8 @@ module.exports = {
|
||||
createLabel: expressify(createLabel),
|
||||
deleteLabel: expressify(deleteLabel),
|
||||
downloadZipOfVersion: expressify(downloadZipOfVersion),
|
||||
getLatestHistory: expressify(getLatestHistory),
|
||||
getChanges: expressify(getChanges),
|
||||
_displayNameForUser,
|
||||
promises: {
|
||||
_pipeHistoryZipToResponse,
|
||||
|
||||
@@ -21,6 +21,12 @@ const projectKey = require('./project_key')
|
||||
|
||||
const GLOBAL_BLOBS = new Set() // CHANGE FROM SOURCE: only store hashes.
|
||||
|
||||
const HISTORY_V1_URL = settings.apis.v1_history.url
|
||||
const HISTORY_V1_BASIC_AUTH = {
|
||||
user: settings.apis.v1_history.user,
|
||||
password: settings.apis.v1_history.pass,
|
||||
}
|
||||
|
||||
function makeGlobalKey(hash) {
|
||||
return `${hash.slice(0, 2)}/${hash.slice(2, 4)}/${hash.slice(4)}`
|
||||
}
|
||||
@@ -144,16 +150,10 @@ async function _deleteProjectInProjectHistory(projectId) {
|
||||
|
||||
async function _deleteProjectInFullProjectHistory(historyId) {
|
||||
try {
|
||||
await fetchNothing(
|
||||
`${settings.apis.v1_history.url}/projects/${historyId}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
basicAuth: {
|
||||
user: settings.apis.v1_history.user,
|
||||
password: settings.apis.v1_history.pass,
|
||||
},
|
||||
}
|
||||
)
|
||||
await fetchNothing(`${HISTORY_V1_URL}/projects/${historyId}`, {
|
||||
method: 'DELETE',
|
||||
basicAuth: HISTORY_V1_BASIC_AUTH,
|
||||
})
|
||||
} catch (err) {
|
||||
throw OError.tag(err, 'failed to clear project history', { historyId })
|
||||
}
|
||||
@@ -162,29 +162,23 @@ async function _deleteProjectInFullProjectHistory(historyId) {
|
||||
async function uploadBlobFromDisk(historyId, hash, byteLength, fsPath) {
|
||||
const outStream = fs.createReadStream(fsPath)
|
||||
|
||||
const url = `${settings.apis.v1_history.url}/projects/${historyId}/blobs/${hash}`
|
||||
const url = `${HISTORY_V1_URL}/projects/${historyId}/blobs/${hash}`
|
||||
await fetchNothing(url, {
|
||||
method: 'PUT',
|
||||
body: outStream,
|
||||
headers: { 'Content-Length': byteLength }, // add the content length to work around problems with chunked encoding in node 18
|
||||
signal: AbortSignal.timeout(60 * 1000),
|
||||
basicAuth: {
|
||||
user: settings.apis.v1_history.user,
|
||||
password: settings.apis.v1_history.pass,
|
||||
},
|
||||
basicAuth: HISTORY_V1_BASIC_AUTH,
|
||||
})
|
||||
}
|
||||
|
||||
async function copyBlob(sourceHistoryId, targetHistoryId, hash) {
|
||||
const url = `${settings.apis.v1_history.url}/projects/${targetHistoryId}/blobs/${hash}`
|
||||
const url = `${HISTORY_V1_URL}/projects/${targetHistoryId}/blobs/${hash}`
|
||||
await fetchNothing(
|
||||
`${url}?${new URLSearchParams({ copyFrom: sourceHistoryId })}`,
|
||||
{
|
||||
method: 'POST',
|
||||
basicAuth: {
|
||||
user: settings.apis.v1_history.user,
|
||||
password: settings.apis.v1_history.pass,
|
||||
},
|
||||
basicAuth: HISTORY_V1_BASIC_AUTH,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -200,7 +194,7 @@ async function requestBlobWithFallback(
|
||||
'overleaf.history.id': true,
|
||||
})
|
||||
// Talk to history-v1 directly to avoid streaming via project-history.
|
||||
let url = new URL(settings.apis.v1_history.url)
|
||||
let url = new URL(HISTORY_V1_URL)
|
||||
url.pathname += `/projects/${project.overleaf.history.id}/blobs/${hash}`
|
||||
|
||||
const opts = { method, headers: { Range: range } }
|
||||
@@ -255,22 +249,14 @@ async function requestBlobWithFallback(
|
||||
* @returns Promise<object>
|
||||
*/
|
||||
async function getCurrentContent(projectId) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
overleaf: true,
|
||||
})
|
||||
const historyId = project?.overleaf?.history?.id
|
||||
if (!historyId) {
|
||||
throw new OError('project does not have a history id', { projectId })
|
||||
}
|
||||
const historyId = await getHistoryId(projectId)
|
||||
|
||||
try {
|
||||
return await fetchJson(
|
||||
`${settings.apis.v1_history.url}/projects/${historyId}/latest/content`,
|
||||
`${HISTORY_V1_URL}/projects/${historyId}/latest/content`,
|
||||
{
|
||||
method: 'GET',
|
||||
basicAuth: {
|
||||
user: settings.apis.v1_history.user,
|
||||
password: settings.apis.v1_history.pass,
|
||||
},
|
||||
basicAuth: HISTORY_V1_BASIC_AUTH,
|
||||
}
|
||||
)
|
||||
} catch (err) {
|
||||
@@ -287,22 +273,14 @@ async function getCurrentContent(projectId) {
|
||||
* @returns Promise<object>
|
||||
*/
|
||||
async function getContentAtVersion(projectId, version) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
overleaf: true,
|
||||
})
|
||||
const historyId = project?.overleaf?.history?.id
|
||||
if (!historyId) {
|
||||
throw new OError('project does not have a history id', { projectId })
|
||||
}
|
||||
const historyId = await getHistoryId(projectId)
|
||||
|
||||
try {
|
||||
return await fetchJson(
|
||||
`${settings.apis.v1_history.url}/projects/${historyId}/versions/${version}/content`,
|
||||
`${HISTORY_V1_URL}/projects/${historyId}/versions/${version}/content`,
|
||||
{
|
||||
method: 'GET',
|
||||
basicAuth: {
|
||||
user: settings.apis.v1_history.user,
|
||||
password: settings.apis.v1_history.pass,
|
||||
},
|
||||
basicAuth: HISTORY_V1_BASIC_AUTH,
|
||||
}
|
||||
)
|
||||
} catch (err) {
|
||||
@@ -314,6 +292,53 @@ async function getContentAtVersion(projectId, version) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest chunk from history
|
||||
*
|
||||
* @param {string} projectId
|
||||
*/
|
||||
async function getLatestHistory(projectId) {
|
||||
const historyId = await getHistoryId(projectId)
|
||||
|
||||
return await fetchJson(
|
||||
`${HISTORY_V1_URL}/projects/${historyId}/latest/history`,
|
||||
{
|
||||
basicAuth: HISTORY_V1_BASIC_AUTH,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get history changes since a given version
|
||||
*
|
||||
* @param {string} projectId
|
||||
* @param {object} opts
|
||||
* @param {number} opts.since - The start version of changes to get
|
||||
*/
|
||||
async function getChanges(projectId, opts = {}) {
|
||||
const historyId = await getHistoryId(projectId)
|
||||
|
||||
const url = new URL(`${HISTORY_V1_URL}/projects/${historyId}/changes`)
|
||||
if (opts.since) {
|
||||
url.searchParams.set('since', opts.since)
|
||||
}
|
||||
|
||||
return await fetchJson(url, {
|
||||
basicAuth: HISTORY_V1_BASIC_AUTH,
|
||||
})
|
||||
}
|
||||
|
||||
async function getHistoryId(projectId) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
overleaf: true,
|
||||
})
|
||||
const historyId = project?.overleaf?.history?.id
|
||||
if (!historyId) {
|
||||
throw new OError('project does not have a history id', { projectId })
|
||||
}
|
||||
return historyId
|
||||
}
|
||||
|
||||
async function injectUserDetails(data) {
|
||||
// data can be either:
|
||||
// {
|
||||
@@ -404,6 +429,8 @@ module.exports = {
|
||||
uploadBlobFromDisk: callbackify(uploadBlobFromDisk),
|
||||
copyBlob: callbackify(copyBlob),
|
||||
requestBlobWithFallback: callbackify(requestBlobWithFallback),
|
||||
getLatestHistory: callbackify(getLatestHistory),
|
||||
getChanges: callbackify(getChanges),
|
||||
promises: {
|
||||
loadGlobalBlobs,
|
||||
initializeProject,
|
||||
@@ -417,5 +444,7 @@ module.exports = {
|
||||
uploadBlobFromDisk,
|
||||
copyBlob,
|
||||
requestBlobWithFallback,
|
||||
getLatestHistory,
|
||||
getChanges,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -151,15 +151,28 @@ function apply(webRouter, privateApiRouter) {
|
||||
|
||||
webRouter.get(
|
||||
'/project/:project_id/latest/history',
|
||||
validate({
|
||||
params: Joi.object({
|
||||
project_id: Joi.objectId().required(),
|
||||
}),
|
||||
}),
|
||||
AuthorizationMiddleware.blockRestrictedUserFromProject,
|
||||
AuthorizationMiddleware.ensureUserCanReadProject,
|
||||
HistoryController.proxyToHistoryApi
|
||||
HistoryController.getLatestHistory
|
||||
)
|
||||
webRouter.get(
|
||||
'/project/:project_id/changes',
|
||||
validate({
|
||||
params: Joi.object({
|
||||
project_id: Joi.objectId().required(),
|
||||
}),
|
||||
query: Joi.object({
|
||||
since: Joi.number().integer().min(0).optional(),
|
||||
}),
|
||||
}),
|
||||
AuthorizationMiddleware.blockRestrictedUserFromProject,
|
||||
AuthorizationMiddleware.ensureUserCanReadProject,
|
||||
HistoryController.proxyToHistoryApi
|
||||
HistoryController.getChanges
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user