Files
Verso/services/web/app/src/Features/GitSync/GitSyncController.mjs
T
claude be8aef44fe
Build and Deploy Verso / deploy (push) Successful in 12m48s
feat(git-sync): independent toggles for project files and PDF push
Two new boolean fields on the project (gitSyncPushFiles, gitSyncPushPdf,
both default true) let users control what gets pushed independently:
- "Push project files" switch — skip all docs/binary files when off
- "Push compiled PDF" switch — grayed out when no pdfPath is set

The push button and auto-push are disabled when both switches would
result in nothing being pushed. Config is stored in MongoDB so settings
persist per-project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:21:37 +00:00

101 lines
3.3 KiB
JavaScript

import logger from '@overleaf/logger'
import GitSyncHandler from './GitSyncHandler.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import { expressify } from '@overleaf/promise-utils'
async function configureGitSync(req, res) {
const projectId = req.params.project_id
const { remoteUrl, subPath = '', pdfPath = '', pushFiles = true, pushPdf = true } = req.body
if (typeof remoteUrl !== 'string' || remoteUrl.trim() === '') {
return res.status(400).json({ error: 'remoteUrl is required' })
}
const trimmedUrl = remoteUrl.trim()
if (!trimmedUrl.startsWith('https://') && !trimmedUrl.startsWith('git@')) {
return res.status(400).json({ error: 'remoteUrl must start with https:// or git@' })
}
const trimmedSubPath = String(subPath).trim().replace(/^\/+|\/+$/g, '')
if (trimmedSubPath.includes('..')) {
return res.status(400).json({ error: 'subPath must not contain ..' })
}
const trimmedPdfPath = String(pdfPath).trim().replace(/^\/+/, '')
if (trimmedPdfPath.includes('..')) {
return res.status(400).json({ error: 'pdfPath must not contain ..' })
}
await GitSyncHandler.setConfig(projectId, {
remoteUrl: trimmedUrl,
subPath: trimmedSubPath,
pdfPath: trimmedPdfPath,
pushFiles: Boolean(pushFiles),
pushPdf: Boolean(pushPdf),
})
logger.debug({ projectId }, 'git sync: config saved')
res.sendStatus(204)
}
async function pushToGit(req, res) {
const projectId = req.params.project_id
try {
const userId = SessionManager.getLoggedInUserId(req.session)
const { buildId, clsiServerId } = req.body ?? {}
const { remoteUrl, subPath, pdfPath, pushFiles, pushPdf } =
await GitSyncHandler.getConfig(projectId)
if (!remoteUrl) {
return res.status(400).json({ error: 'No git remote configured for this project' })
}
logger.debug({ projectId }, 'git sync: starting push')
await GitSyncHandler.pushToRemote(projectId, remoteUrl, subPath, {
pdfPath,
pdfBuildId: buildId,
pdfClsiServerId: clsiServerId,
userId,
pushFiles,
pushPdf,
})
res.sendStatus(204)
} catch (err) {
logger.warn({ err, projectId }, 'git sync: push failed')
res.status(500).json({ error: err.message })
}
}
async function pullFromGit(req, res) {
const projectId = req.params.project_id
try {
const userId = SessionManager.getLoggedInUserId(req.session)
const { remoteUrl, subPath } = await GitSyncHandler.getConfig(projectId)
if (!remoteUrl) {
return res.status(400).json({ error: 'No git remote configured for this project' })
}
logger.debug({ projectId }, 'git sync: starting pull')
await GitSyncHandler.pullFromRemote(projectId, remoteUrl, subPath, userId)
res.sendStatus(204)
} catch (err) {
logger.warn({ err, projectId }, 'git sync: pull failed')
res.status(500).json({ error: err.message })
}
}
async function getGitSyncConfig(req, res) {
const projectId = req.params.project_id
const config = await GitSyncHandler.getConfig(projectId)
res.json({
remoteUrl: config.remoteUrl ?? '',
subPath: config.subPath,
pdfPath: config.pdfPath,
pushFiles: config.pushFiles,
pushPdf: config.pushPdf,
})
}
export default {
configureGitSync: expressify(configureGitSync),
pushToGit: expressify(pushToGit),
pullFromGit: expressify(pullFromGit),
getGitSyncConfig: expressify(getGitSyncConfig),
}