v4.3.38: 법률 어시스턴트 앱에 사건(사건 기록) 관리 기능 추가
- 채팅창 파일 첨부 버튼 추가 (PDF/TXT, 회계사 앱과 같은 /api/upload/file 재사용, pdf_read로 계약서 분석 — 스킬 프롬프트엔 이미 방법론이 있었는데 버튼만 없었음) - 탐정 앱의 "사건" 관리를 법률 앱에 이식: 사건 목록(검색/정렬) + 사건별 메모· 체크리스트·타임라인·첨부자료·제출서류 탭 + 사건별 독립 AI 대화 세션 (다른 사건 컨텍스트가 안 섞임, 탐정 앱과 동일 패턴) - PDF 편집기(Stirling-PDF)·Collabora 문서 편집 연동도 그대로 이식 — 사건 폴더에 자동 저장됨 - 백엔드는 탐정 앱 기존 코드를 건드리지 않고 새 공용 모듈(case-storage.ts)로 일반화 — appType 파라미터로 detective/lawyer 둘 다 같은 로직 재사용, 탐정 앱 데이터·동작은 그대로 유지(회귀 테스트로 확인) - 사진 자동 날짜인식(탐정 앱 전용, 카톡 캡처 등에서 날짜 추출) 등 법률 사건과 무관한 기능은 이식 범위에서 제외 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+19
-7
@@ -81,7 +81,7 @@ import { registerUserAppSessionRoutes } from './routes/routes-user-app-sessions'
|
||||
import { registerChannelMappingRoutes } from './routes/routes-channel-mappings';
|
||||
import { registerAdminSessionRoutes } from './routes/routes-admin-sessions';
|
||||
import { registerUserRoutes } from './routes/routes-users';
|
||||
import { registerDetectiveRoutes, detectiveFilesDir, resolveDetectiveUploadPath } from './routes/routes-detective';
|
||||
import { registerDetectiveRoutes } from './routes/routes-detective';
|
||||
import { registerPptxRoutes } from './routes/routes-pptx';
|
||||
import { registerVoiceRoutes } from './routes/routes-voice';
|
||||
import { registerVoiceRealtimeRoutes, attachVoiceRealtimeWsProxy } from './routes/routes-voice-realtime';
|
||||
@@ -91,6 +91,8 @@ import { registerLanguageRoutes } from './routes/routes-language';
|
||||
import { registerWriterRoutes } from './routes/routes-writer';
|
||||
import { registerDentalRoutes } from './routes/routes-dental';
|
||||
import { registerDetectiveDateRoutes } from './routes/routes-detective-dates';
|
||||
import { registerLawyerCaseRoutes } from './routes/routes-lawyer-cases';
|
||||
import { caseFilesDir, resolveUploadPath, registerWopiRoutes } from './routes/case-storage';
|
||||
import {
|
||||
getConfig,
|
||||
getAgents,
|
||||
@@ -2676,12 +2678,16 @@ app.use('/wavacity', express.static(path.join(webUiPath, 'wavacity'), {
|
||||
// URL with no query string. (Stirling-PDF's root path has a bug where a
|
||||
// query string on the bare context-path root causes a broken redirect —
|
||||
// see /pdf-editor below — so the case id is never sent in that request.)
|
||||
// Cookie value is "<appType>:<caseId>" so the same Stirling-PDF instance and
|
||||
// auto-save logic serve any case-backed app (detective, lawyer, ...) — app
|
||||
// defaults to 'detective' when omitted, for links that predate the lawyer app.
|
||||
app.get('/pdf-editor-open', (req: express.Request, res: express.Response) => {
|
||||
const session = getSessionUser(req);
|
||||
if (!session) { res.redirect('/html/login.html'); return; }
|
||||
const caseId = String(req.query.caseId || '');
|
||||
if (/^[a-zA-Z0-9_-]+$/.test(caseId)) {
|
||||
res.setHeader('Set-Cookie', `dt_pdf_case=${caseId}; Path=/pdf-editor; HttpOnly; SameSite=Lax; Max-Age=3600`);
|
||||
const appType = String(req.query.app || 'detective');
|
||||
if (/^[a-zA-Z0-9_-]+$/.test(caseId) && /^[a-z]+$/.test(appType)) {
|
||||
res.setHeader('Set-Cookie', `dt_pdf_case=${appType}:${caseId}; Path=/pdf-editor; HttpOnly; SameSite=Lax; Max-Age=3600`);
|
||||
}
|
||||
res.redirect('/pdf-editor/');
|
||||
});
|
||||
@@ -2697,7 +2703,11 @@ app.get('/pdf-editor-open', (req: express.Request, res: express.Response) => {
|
||||
// the browser as normal, so "download" in the editor also lands in the case.
|
||||
app.use('/pdf-editor', (req: express.Request, res: express.Response) => {
|
||||
const cookies = parseCookies(req);
|
||||
const caseId = cookies['dt_pdf_case'] || '';
|
||||
const rawCookie = cookies['dt_pdf_case'] || '';
|
||||
const colonIdx = rawCookie.indexOf(':');
|
||||
// No colon = old-format cookie set before app-scoping existed — treat as detective.
|
||||
const appType = colonIdx >= 0 ? rawCookie.slice(0, colonIdx) : 'detective';
|
||||
const caseId = colonIdx >= 0 ? rawCookie.slice(colonIdx + 1) : rawCookie;
|
||||
const session = getSessionUser(req);
|
||||
|
||||
const proxyReq = http.request({
|
||||
@@ -2721,16 +2731,16 @@ app.use('/pdf-editor', (req: express.Request, res: express.Response) => {
|
||||
proxyRes.on('end', () => {
|
||||
const body = Buffer.concat(chunks);
|
||||
try {
|
||||
const dir = detectiveFilesDir(session!.username, caseId);
|
||||
const dir = caseFilesDir(session!.username, appType, caseId);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
let filename = 'edited.pdf';
|
||||
const fnStar = cd.match(/filename\*=UTF-8''([^;]+)/i);
|
||||
const fnPlain = cd.match(/filename="?([^";]+)"?/i);
|
||||
if (fnStar) { try { filename = decodeURIComponent(fnStar[1]); } catch {} }
|
||||
else if (fnPlain) filename = fnPlain[1];
|
||||
const { relPath, absPath } = resolveDetectiveUploadPath(dir, filename);
|
||||
const { relPath, absPath } = resolveUploadPath(dir, filename);
|
||||
fs.writeFileSync(absPath, body);
|
||||
console.log(`[pdf-editor] saved result to case ${caseId}: ${relPath}`);
|
||||
console.log(`[pdf-editor] saved result to ${appType} case ${caseId}: ${relPath}`);
|
||||
} catch (err) {
|
||||
console.error('[pdf-editor] auto-save failed:', err);
|
||||
}
|
||||
@@ -4357,6 +4367,8 @@ registerUserRoutes(app, {
|
||||
CONFIG_DIR_PATH,
|
||||
});
|
||||
registerDetectiveRoutes(app, getSessionUser, isPathInsideDir, CONFIG_DIR_PATH);
|
||||
registerLawyerCaseRoutes(app, getSessionUser, isPathInsideDir);
|
||||
registerWopiRoutes(app, '/api/lawyer');
|
||||
|
||||
|
||||
// Redirect legacy top-level HTML routes into /html/ after web-ui reorganization.
|
||||
|
||||
Reference in New Issue
Block a user