Files
UNITRONIX dd0889e3d6 Add SessionManager; update docs, i18n & CI
Introduce a SessionManager for relay-based remote sessions in the Tauri MGMT client: new SessionCommand API, start/stop/session input routing, clipboard/recording/quality controls, and notification read/dismiss state. Wire AppState with new mutexes and show main window on startup. CI: add SBOM generation (anchore) and Trivy vulnerability scan steps. Misc: change console Docker DB path, large README/CHANGELOG updates (chat E2E, unattended access/WOL, i18n expansion, CDAP/SDK docs), and many web-nodejs assets/locales/routes/views/services and server-side changes.
2026-04-05 13:42:07 +02:00

101 lines
3.2 KiB
JavaScript

/**
* BetterDesk Console — Chat Routes
* Dedicated chat page + API endpoints for Chat 2.0
*/
'use strict';
const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const { requireAuth } = require('../middleware/auth');
// File upload config — max 50MB, store in data/chat-files/
const UPLOAD_DIR = path.join(__dirname, '..', 'data', 'chat-files');
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}
const upload = multer({
storage: multer.diskStorage({
destination: (_req, _file, cb) => cb(null, UPLOAD_DIR),
filename: (_req, file, cb) => {
const id = crypto.randomBytes(16).toString('hex');
const ext = path.extname(file.originalname).slice(0, 10);
cb(null, `${id}${ext}`);
},
}),
limits: { fileSize: MAX_FILE_SIZE },
});
// ========== Page Route ==========
router.get('/chat', requireAuth, (req, res) => {
res.render('chat', {
title: req.t('nav.chat'),
pageStyles: ['chat'],
pageScripts: ['chat'],
currentPage: 'chat',
breadcrumb: [{ label: req.t('nav.chat') }],
});
});
// ========== API Routes ==========
// Upload encrypted file
router.post('/api/chat/upload', requireAuth, upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ success: false, error: 'No file provided' });
}
const fileId = path.basename(req.file.filename, path.extname(req.file.filename));
res.json({
success: true,
file_id: fileId,
filename: req.file.filename,
size: req.file.size,
url: `/api/chat/files/${fileId}`,
});
});
// Download file
router.get('/api/chat/files/:fileId', requireAuth, (req, res) => {
const fileId = req.params.fileId.replace(/[^a-f0-9]/gi, '');
if (!fileId) return res.status(400).json({ success: false, error: 'Invalid file ID' });
// Find file by ID prefix
const files = fs.readdirSync(UPLOAD_DIR).filter(f => f.startsWith(fileId));
if (files.length === 0) {
return res.status(404).json({ success: false, error: 'File not found' });
}
const filePath = path.join(UPLOAD_DIR, files[0]);
// Verify path traversal protection
if (!filePath.startsWith(UPLOAD_DIR)) {
return res.status(403).json({ success: false, error: 'Forbidden' });
}
res.download(filePath);
});
// Search messages via Go server proxy
router.get('/api/chat/search', requireAuth, async (req, res) => {
try {
const { apiClient } = require('../services/betterdeskApi');
const query = String(req.query.q || '').slice(0, 200);
const conversationId = req.query.conversation_id || '';
if (!query) return res.json({ success: true, messages: [] });
const resp = await apiClient.get('/api/chat/search', {
params: { q: query, conversation_id: conversationId, limit: 50 },
});
res.json({ success: true, messages: resp.data?.messages || [] });
} catch (e) {
res.json({ success: true, messages: [] });
}
});
module.exports = router;