var express = require('express'); var router = express.Router(); var axios = require('axios'); var { authMiddleware } = require('../middleware/auth'); var db = require('../db/database'); var logger = require('../utils/logger'); var cryptoUtil = require('../utils/crypto'); var { serverError } = require('../utils/errors'); var { assertSafeHttpsUrl } = require('../utils/urlSafety'); router.use('/nextcloud', authMiddleware, require('../utils/policy').requireFeature('nextcloud')); function davRoot(baseUrl, username) { return baseUrl + '/remote.php/dav/files/' + encodeURIComponent(username); } router.post('/nextcloud/connect', authMiddleware, async function(req, res) { try { var { nextcloudUrl, username, appPassword, folder } = req.body; if (!nextcloudUrl || !username || !appPassword) return res.status(400).json({ error: 'All fields required' }); var cleanUrl = nextcloudUrl.replace(/\/+$/, ''); var targetFolder = (folder || '/PediatricScribe').replace(/\/+$/, ''); await assertSafeHttpsUrl(cleanUrl, 'Nextcloud URL'); try { await axios({ method: 'PROPFIND', url: davRoot(cleanUrl, username) + '/', auth: { username: username, password: appPassword }, headers: { Depth: '0' }, timeout: 15000, maxRedirects: 0 }); } catch (e) { return res.status(400).json({ error: 'Cannot connect. Check credentials.' }); } var parts = targetFolder.split('/').filter(Boolean); var current = ''; for (var i = 0; i < parts.length; i++) { current += '/' + parts[i]; try { await axios({ method: 'MKCOL', url: davRoot(cleanUrl, username) + current + '/', auth: { username: username, password: appPassword }, timeout: 15000, maxRedirects: 0 }); } catch (e) {} } await db.run('UPDATE users SET nextcloud_url = ?, nextcloud_user = ?, nextcloud_token = ?, nextcloud_folder = ? WHERE id = ?', [cleanUrl, username, cryptoUtil.encryptString(appPassword), targetFolder, req.user.id]); res.json({ success: true, message: 'Connected! Files saved to ' + targetFolder + '/' }); logger.audit(req.user.id, 'nextcloud_connect', 'Connected Nextcloud', req, { category: 'integration' }); } catch (err) { return serverError(res, 'Nextcloud connect', err, 'Could not connect to Nextcloud'); } }); router.post('/nextcloud/export', authMiddleware, async function(req, res) { try { var { content, filename, type } = req.body; var user = await db.get('SELECT nextcloud_url, nextcloud_user, nextcloud_token, nextcloud_folder FROM users WHERE id = ?', [req.user.id]); if (!user || !user.nextcloud_url) return res.status(400).json({ error: 'Nextcloud not connected. Go to Settings.' }); await assertSafeHttpsUrl(user.nextcloud_url, 'Nextcloud URL'); var baseFolder = user.nextcloud_folder || '/PediatricScribe'; var today = new Date().toISOString().split('T')[0]; var targetPath = baseFolder + '/' + today; var pw; try { pw = cryptoUtil.decryptString(user.nextcloud_token); } catch (decErr) { return res.status(400).json({ error: 'Nextcloud credentials invalid. Please reconnect.' }); } var parts = targetPath.split('/').filter(Boolean); var current = ''; for (var i = 0; i < parts.length; i++) { current += '/' + parts[i]; try { await axios({ method: 'MKCOL', url: davRoot(user.nextcloud_url, user.nextcloud_user) + current + '/', auth: { username: user.nextcloud_user, password: pw }, timeout: 15000, maxRedirects: 0 }); } catch (e) {} } var time = new Date().toTimeString().split(' ')[0].replace(/:/g, '-'); var safeName = (filename || type + '-' + time).replace(/[^a-zA-Z0-9-_]/g, '_'); var filePath = davRoot(user.nextcloud_url, user.nextcloud_user) + targetPath + '/' + safeName + '.txt'; await axios({ method: 'PUT', url: filePath, data: content, auth: { username: user.nextcloud_user, password: pw }, headers: { 'Content-Type': 'text/plain; charset=utf-8' }, timeout: 30000, maxRedirects: 0 }); // Migrate legacy plaintext token to encrypted form on first successful use. if (!cryptoUtil.isEncrypted(user.nextcloud_token)) { db.run('UPDATE users SET nextcloud_token = ? WHERE id = ?', [cryptoUtil.encryptString(pw), req.user.id]).catch(function(){}); } res.json({ success: true, message: 'Saved to ' + targetPath + '/' + safeName + '.txt' }); logger.audit(req.user.id, 'nextcloud_export', 'Exported note to Nextcloud', req, { category: 'export' }); } catch (err) { return serverError(res, 'Nextcloud export', err, 'Could not export to Nextcloud'); } }); router.post('/nextcloud/disconnect', authMiddleware, async function(req, res) { try { await db.run('UPDATE users SET nextcloud_url = NULL, nextcloud_user = NULL, nextcloud_token = NULL, nextcloud_folder = NULL WHERE id = ?', [req.user.id]); res.json({ success: true }); logger.audit(req.user.id, 'nextcloud_disconnect', 'Disconnected Nextcloud', req, { category: 'integration' }); } catch (err) { return serverError(res, 'Nextcloud disconnect', err, 'Could not disconnect'); } }); module.exports = router;