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, branch = 'main', } = 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 ..' }) } const trimmedBranch = String(branch).trim() if (!/^[a-zA-Z0-9/_.-]+$/.test(trimmedBranch) || trimmedBranch.includes('..')) { return res.status(400).json({ error: 'Invalid branch name' }) } await GitSyncHandler.setConfig(projectId, { remoteUrl: trimmedUrl, subPath: trimmedSubPath, pdfPath: trimmedPdfPath, pushFiles: Boolean(pushFiles), pushPdf: Boolean(pushPdf), branch: trimmedBranch, }) 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, branch } = 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, branch, }) 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, branch: config.branch, }) } export default { configureGitSync: expressify(configureGitSync), pushToGit: expressify(pushToGit), pullFromGit: expressify(pullFromGit), getGitSyncConfig: expressify(getGitSyncConfig), }