Files
ai_platform/README.md
dsyoon 073a8343dd feat: xavis ai_platform 기능 이전 및 ncue 환경 전환
xavis 소스·DB 스키마·활용사례/F-Scan/프롬프트 라이브러리 등 기능 반영.
@xavis.co.kr → @ncue.net, 관리자 토큰 ncue-admin, 런타임 data/ Git 추적 제외.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 22:27:48 +09:00

51 KiB
Raw Blame History

Web Platform (학습센터)

유튜브 링크와 PowerPoint(.pptx) 강의를 등록하고, 목록에서 클릭해 바로 시청할 수 있는 사내 학습센터 예제입니다.

소스 저장소(Git): https://git.xavis.co.kr/AI_Innovation_Team/ai_platform


접속 방법

브라우저에서 열기 전에 반드시 터미널에서 서버를 실행해야 합니다. (npm start 미실행 상태에서는 http://localhost:8030에 연결되지 않습니다.)

서버 실행 후 아래 주소로 접속합니다.

환경 URL
로컬 기본 http://localhost:8030
포트 변경 시 http://localhost:{PORT} (예: PORT=4000 npm starthttp://localhost:4000)
  • 메인 페이지 (/): 학습센터 뷰어 (강의 목록)
  • 학습센터 뷰어 (/learning): 강의 검색/필터 + 카드 목록 (일반 사용자)
  • 관리자 (/admin): 강의 등록(YouTube/PPT/웹 링크 등), 썸네일·AI 추가 등 통합 관리
  • 강의 상세: 카드 클릭 시 유튜브 재생 또는 PPT 뷰어
  • 기타 메뉴: 회사규정(/chat 리다이렉트), WM(/wm 리다이렉트), AI(/ai-explore), 프롬프트(/ai-explore/prompts, 좌측 전역 메뉴), AI 활용 사례(/ai-cases), 대시보드(/dashboard, AI 활용 사례 아래 메뉴), AX 과제 신청(/ax-apply)
  • 관리자 이벤트 로그: http://localhost:8030/admin/thumbnail-events?token={ADMIN_TOKEN}

접속이 안 될 때 (트러블슈팅)

  1. 서버가 떠 있는지 확인
    프로젝트 루트에서 npm start를 실행하고, 터미널에 Server started: http://localhost:8030 같은 로그가 나오는지 확인합니다.
  2. 포트 번호 확인
    .envPORT=...가 있으면 그 포트로 접속합니다. (기본값은 8030)
  3. 부팅 실패
    터미널에 Failed to bootstrap:이 나오면 프로세스가 종료되며 HTTP 서버가 뜨지 않습니다. 메시지를 확인한 뒤 data/ 쓰기 권한, 디스크 여유, DB 설정 등을 점검합니다.
  4. 포트 충돌
    다른 프로그램이 8030을 쓰는 경우 PORT=8031 npm start처럼 바꿔 실행하거나, 이미 떠 있는 Node 서버 프로세스를 종료합니다. 구체적인 명령은 아래 실행 방법 절의 기존 서버 프로세스 종료 항목을 참고하세요.
  5. 바인드 주소
    기본은 모든 네트워크 인터페이스(0.0.0.0)에서 수신합니다. 로컬 루프백만 쓰려면 HOST=127.0.0.1 npm start를 사용할 수 있습니다.

배포 서버: git pulldata/ 런타임 파일 때문에 막힐 때

원격이 data/(예: data/ai-success-stories.json)를 Git 추적에서 제외하는 쪽으로 바뀌는 동안, 서버에 수정해 둔 같은 경로가 있으면 Your local changes to the following files would be overwritten by merge 메시지와 함께 git pull이 중단될 수 있습니다.

  • 권장: 프로젝트 루트에서 bash scripts/safe-git-pull.sh를 실행합니다. 백업 → 로컬 변경만 Git 관점에서 정리 → git pull → 백업 복원 순서로, 서비스에 쓰는 JSON 내용을 유지합니다. main이 반영된 뒤에는 해당 파일이 무시(ignore) 대상이 되어, 이후에는 보통 git pull만으로 갱신됩니다.
  • 수동: 파일을 임시로 복사한 뒤 git restore data/ai-success-stories.json(구버전은 git checkout -- data/ai-success-stories.json) → git pull → 필요 시 다시 복사합니다.

핵심 기능

  • 학습센터 UI (좌측 메뉴 + 상단 헤더 + 강의 카드 레이아웃)
  • AI 탐색 (/ai-explore): 전체 너비 레이아웃, AI 서비스 카드(회의록·체크리스트 등; 프롬프트 라이브러리는 좌측 프롬프트 메뉴에서 진입)
  • 회의록 AI (/ai-explore/meeting-minutes): 텍스트 입력 탭에서 회의 원문복사 버튼(생성 결과의 회의록·전사와 동일한 연한 녹색 스타일)으로 원문을 클립보드에 복사할 수 있습니다. 회의록 생성 시스템 프롬프트는 lib/meeting-minutes.jsbuildMeetingMinutesSystemPrompt에서 구성하며, 기본 구조 중 「2) 참석·언급 인원」은 표 없이 한 줄 쉼표 목록(원문 근거 인명·직함·조직명)으로 안내합니다. 저장된 사용자 추가 지시(비어 두면 해당 블록 없음)만 시스템 프롬프트 우선 순위 블록에 합류합니다. 화면 음성 업로드 생성 시 처리 상태를 1 업로드 / 2 전사 / 3 번역(전사 결과를 회의록 형식으로 LLM이 정리하는 단계)으로 나누어 진행 표시 및 SSE 패딩·하트비트로 장시간 작업 안내를 제공합니다. 음성 UI는 브라우저에서 두 단계로 동작합니다. POST /api/meeting-minutes/prepare-audio(multipart 필드: audio, title, meetingDate, model, whisperModel · whisperModel 생략 시 서버 기본 전사는 gpt-4o-transcribe, 필요 시 OPENAI_WHISPER_MODEL) 요청 시 **XHR upload.onprogress**로 업로드 바이트를 표시하고, 수신 즉시 { jobId } JSON을 돌려줍니다. 이어 같은 세션으로 **GET /api/meeting-minutes/stream-audio/:jobId**를 fetch(ReadableStream)로 열어 text/event-stream 전사·회의록 SSE를 증분 수신합니다. 업로드와 SSE가 한 HTTP 요청으로 묶이면(레거시 POST …/generate-audio) 브라우저·역프록시가 응답 본문을 버퍼링해 진행이 멈춘 것처럼 보일 수 있어, 위 분리 플로를 권장합니다. 작업 메타는 data/meeting-audio-job-meta/(실행 시 생성, 저장소에서는 data/가 무시)에 두며 TTL은 MEETING_AUDIO_JOB_TTL_MS(미설정 시 약 1시간, 상한·하한은 코드 참고). 로드밸런스 뒤에 Node 인스턴스가 여러 대이면 이 data/ 디렉터리(또는 job 메타 저장소)와 업로드 디렉터리가 인스턴스 간 공유되어야 같은 jobId에 대해 prepare와 stream 요청이 같은 파일·메타를 읽을 수 있습니다. 클라이언트에서는 consumeSseFromFetch가 이벤트마다 **mmAudioStreamHandlers.onSseEvent**를 호출합니다(views/meeting-minutes.ejs). 디버깅 시 같은 페이지 콘솔에서 getMeetingMinutesAudioDiag()를 호출하면 uploadPhase, lastSseEvent, 세그먼트 진행 등을 확인할 수 있습니다. 회의 결과 저장 시 lib/parse-checklist-from-minutes.js액션 아이템 마크다운 표(GFM 권장)와 번호 목록을 규칙으로 읽은 뒤 extractChecklistStructured(LLM) 결과와 서버에서 병합하여 업무 체크리스트로 반영합니다(MEETING_AUTO_CHECKLIST=0 등으로 끌 수 있음). 동일 처리 경로에서 prepareMeetingMinutesForApi헤더·본문 열 수 불일치(헤더 3열 대 데이터 4열 등)로 깨진 액션 표를 교정하고, 「담당」열에 들어 간 발언자·저희·우리 팀 등 통칭·역할 명칭 단독은 규칙 후처리 시 미정으로 바꿀 수 있습니다. DB meeting_ai_prompts.include_checklisttrue일 때만 회의 체크리스트 강제 블록을 넣고, 기본값은 false입니다. include_checklistfalse이면 생성 직후 prepareMeetingMinutesForApi에서 ## 회의 체크리스트 등 블록을 후처리로 제거합니다(모델이 습관적으로 넣은 경우 대비). 기존 DB에 include_checklist = true가 남아 있으면 UPDATE meeting_ai_prompts SET include_checklist = false로 끄거나, 화면에서 프롬프트 저장으로 덮어씁니다.
  • 임직원 인명 정규화: data/meeting-employee-names.txt(또는 MEETING_EMPLOYEE_NAMES_FILE)에 성명을 두고, lib/meeting-employee-names.js가 전사·원문에서 이름으로 보이는 토큰만 명단과 퍼지 매칭해, 회의록 LLM 요청 사용자 메시지 상단에 짧은「이번 원문/전사 한정 · 임직원 표기 통일」블록만 붙입니다. 전 직원 명단을 시스템 프롬프트에 넣지 않습니다. 끄려면 MEETING_NAME_NORMALIZATION=0.
  • 대시보드 (/dashboard): AI 탐색과 유사한 카드 그리드·검색으로 대시보드를 모아 표시. 첫 카드 경영성과 대시보드/dashboard/business-performance로 연결
  • 경영성과 대시보드 (/dashboard/business-performance): 위쪽 대시보드 조회(Chart.js 인라인), 아래 엑셀 업로드(.xlsx, 매출일보 시트) 순서. 본문은 AI 프롬프트 페이지와 동일하게 main.container.container-ai-full(전체 너비·좌우 24px)로 맞춤. 대시보드 상단 연도·분기mgmt_perf_uploads에 저장된 해당 기간 최신 스냅샷을 불러오며, 쿼리 ?year=2026&quarter=1 또는 폼 조회와 동일. 해당 기간 업로드가 없으면 기본 JSON 샘플을 쓰고 안내 문구를 표시합니다. public/mgmt-perf/dashboard.css에 있던 범용 .container { background: white } 는 앱 페이지에서 main.container까지 적용되어 회색 본문이 가려졌으므로 제거하고, 흰 카드는 .mgmt-perf-embed .container 만 사용합니다. 업로드는 DB(mgmt_perf_uploads / mgmt_perf_snapshots) 또는 DB 미연결 시 data/mgmt-perf-last-state.json에 스냅샷 저장. 최근 업로드 행 DELETE /api/mgmt-perf/upload/:id 로 삭제(PG는 CASCADE, 파일 전용 모드는 id=file). 단독 임베드 페이지는 /dashboard/business-performance/embed(본문에 body.mgmt-perf-standalone으로 어두운 배경). Express에서 /mgmt-perf/*public/mgmt-perf/ 정적 제공이 등록되어 있어 dashboard-app.js·chart.umd.min.js(CDN 대신 동봉)가 항상 같은 오리진에서 로드됩니다. 업로드 시 한글 파일명은 multer 기본(defParamCharset 생략 시 latin1)으로 온 originalname을 **lib/decode-upload-filename.js**의 decodeUploadFilename으로 보정합니다(decodeURIComponent(escape(...)) 우선, 이어서 Buffer latin1→utf8). Busboy에 defParamCharset: 'utf8'를 켜면 이중 디코딩으로 깨질 수 있어 두지 않습니다. 탭 전환·차트 렌더는 ASCII 섹션 id(mgmt-sec-sales 등)와 state.currentSection으로 동기화합니다. 리버스 프록시로 Apache2를 쓰는 경우 업로드 실패·413이면 VirtualHost LimitRequestBody(예: 바이트 단위로 deploy/apache-ai.ncue.net-ssl.conf.example와 맞출 것)와 /api/·/mgmt-perf/ → Node ProxyPass 전달 여부를 확인. (Nginx를 쓰는 환경이면 client_max_body_size 등을 해당 서버 설정에 맞춥니다.) 엑셀 집계 치환은 npm installxlsx 설치 후 서버 재시작.
  • 경영성과 데이터 확인: 브라우저에서 GET /api/mgmt-perf/status(JSON)로 최근 스냅샷의 payloadKeys, _uploadMeta(행 수 등)를 확인할 수 있습니다. 현재 구현은 엑셀에서 매출일보 행 수·시트명만 payload._uploadMeta에 넣고, 차트 수치는 기본 시드 JSON(config/mgmt-perf-default-payload.json)을 씁니다. 5,000행이어도 차트가 엑셀 집계와 일치하려면 별도 집계·매핑 로직이 필요합니다.
  • 대시보드 메뉴 접근: .envDASHBOARD_MENU_ALLOWED_EMAILS쉼표로 구분한 OPS 로그인 이메일만 좌측 대시보드 메뉴·/dashboard·경영성과 API가 보입니다. 목록이 비어 있으면 누구에게도 표시되지 않습니다. 로컬(DEV)에서 관리자 토큰만 쓰는 경우 DASHBOARD_MENU_DEV_USE_MEETING_EMAIL=1MEETING_DEV_EMAIL을 허용 목록과 맞추면 대조됩니다.
  • 프롬프트 라이브러리 (/ai-explore/prompts): 좌측 프롬프트 메뉴로 진입 · 업무별 기본 프롬프트 카드 선택·미리보기·클립보드 복사 (config/company-prompts.json) · 공유하기 탭의 본문은 원문 보기 / 마크다운 보기 토글(클라이언트 marked + DOMPurify) · 워크플로 탭(①~④ 입력·초안 합치기·AI로 다듬기). 좌측 메뉴(채팅·AI·프롬프트·AI 활용 사례·학습센터·AX 과제 신청)는 관리자 여부와 관계없이 접근 가능(강의 삭제·관리자 대시보드 등 일부 기능은 관리자 모드에서만)
  • 검색/필터/페이지네이션
    • 검색어(q) 기반 제목/설명/태그 필터
    • 타입(YouTube, PPT) 필터
    • 태그 필터 + 페이지네이션
  • 유튜브 강의 등록/시청
    • 유튜브 URL 입력 후 목록에 추가
    • 강의 상세에서 iframe 임베드로 재생
  • PPT 강의 등록/시청
    • .pptx 파일 업로드
    • 상세에서 슬라이드 이미지(PNG)만 표시(XML 텍스트 추출 목록은 표시하지 않음)
    • PPT/PDF(.pdf) 상세: 1단·2단·3단 보기 전환(그리드), 선택값은 브라우저 localStorage에 저장
    • 목록 카드에 PPT 프리뷰(첫 슬라이드 제목 + 장수) 표시
    • macOS 환경에서는 qlmanage 기반 실제 썸네일(첫 장 이미지) 자동 생성
  • 업로드 동영상(mp4/webm/mov 등): 목록 카드에 ffmpeg로 뽑은 대표 프레임 썸네일 표시(기본 약 0.5초 지점). 서버에 ffmpeg가 있어야 하며, PPT 썸네일과 동일한 백그라운드 큐·재시도 정책을 사용합니다.
  • 썸네일 백그라운드 큐
    • 썸네일 생성은 비동기 큐에서 처리
    • 상태값: pending / processing / ready / failed
    • 실패 시 자동 재시도 정책 적용(최대 횟수 이후 failed 고정)
    • 큐 스냅샷을 data/thumbnail-jobs.json에 저장해 재시작 후 복구
    • 작업 이벤트를 data/thumbnail-events.json에 기록
  • 관리자 삭제
    • 관리자 토큰으로 강의 삭제 가능
  • 초기 샘플 데이터 시드
    • resources/lecture에 있는 .pptx를 최초 실행 시 자동 등록
  • 관리자 인증·바로가기(UI)
    • 좌측 메뉴 하단 관리자 / 관리자 off: 운영(OPS)에서 이메일 인증으로 로그인한 임직원에게도 표시됩니다. **관리자**는 모달에서 ADMIN_TOKEN을 입력해 검증한 뒤 /admin으로 이동합니다(POST /api/admin/validate-token). **관리자 off**는 관리자 쿠키를 지우고 학습센터 목록(/learning)으로 돌아갑니다(GET /admin/logout). 이메일 미로그인 환경에서도 동일합니다. 이미 관리자 세션이면 중복이므로 관리자 항목은 숨기고, 사용자 현황관리 → 구분선 → 관리자 off →(OPS일 때 구분선)→ 로그아웃 순으로 표시됩니다.
    • 학습센터 (/learning): 관리자 쿠키가 있을 때 상단 오른쪽 학습 등록으로 통합 관리 화면(/admin)에 들어갈 수 있습니다. 이메일(OPS) 로그인과 동시에 있어도 버튼이 숨겨지지 않습니다.
    • AI 활용 사례 (/ai-cases): 관리자일 때 상단 사례 등록·관리로 편집 화면(/ai-cases/write)에 진입합니다(동일하게 OPS 로그인 중에도 표시).

프로젝트 구조

ai_platform/
├─ server.js                  # Express 서버 진입점 (라우팅, 업로드, 썸네일 큐, DB 연동)
├─ package.json               # 의존성 및 npm 스크립트
├─ .env                       # 환경 변수 (실제 값, .gitignore 대상)
├─ .env.example               # 환경 변수 예시 템플릿
├─ db/
│  └─ schema.sql              # PostgreSQL 스키마 (강의·회의록·경영성과·프롬프트 라이브러리 좋아요/공유 등, 기동 시 자동 적용)
├─ scripts/
│  ├─ apply-schema.js         # 수동 스키마 적용 (npm run db:schema)
│  ├─ pg-backup.sh            # PostgreSQL 논리 백업 (cron·npm run db:backup)
│  ├─ pg-restore.sh           # 백업 복원 (npm run db:restore -- …)
│  └─ lib/load-env.sh         # 셸 스크립트용 .env 로더
├─ public/
│  └─ styles.css              # 전역 스타일(회의록 마크다운 뷰 `.mm-minutes-rendered` 표 셀 테두리 등)
├─ views/
│  ├─ partials/
│  │  ├─ nav.ejs              # 좌측 공통 네비게이션(하단 관리자·토큰 모달 연동)
│  │  └─ admin-token-modal.ejs # 관리자 토큰 입력 모달(`openAdminTokenModal`)
│  ├─ learning-viewer.ejs     # 학습센터 뷰어 (일반 사용자)
│  ├─ learning-admin.ejs      # 학습센터 관리 (업로드·삭제·썸네일)
│  ├─ chat.ejs                # OpenAI 기반 인앱 채팅 UI
│  ├─ ai-explore.ejs          # AI 탐색 (전체 너비, 회의록 등 카드·검색; 프롬프트는 좌측 메뉴)
│  ├─ dashboard.ejs           # 대시보드 목록(카드·검색)
│  ├─ dashboard-business-performance.ejs  # 경영성과(업로드 + 인라인 Chart.js 조회)
│  ├─ mgmt_perf_embed.ejs       # 경영성과 차트 단독 페이지(직접 열람·임베드용)
│  ├─ partials/mgmt_perf_dashboard_container.ejs
│  ├─ ai-prompts.ejs          # 프롬프트 라이브러리 (카드·미리보기·복사)
│  ├─ ai-cases.ejs            # AI 활용 사례 목록(카드·`data/ai-success-stories.json` + PG `ai_use_case_submissions` 병합)
│  ├─ ai-use-case-submission-detail.ejs  # 일반 제출 상세(`GET /ai-cases/submit/:uuid`)
│  ├─ ai-case-detail.ejs       # AI 활용 사례 상세(마크다운 또는 PDF·페이지 이미지, 1·2·3단 보기)
│  ├─ ai-cases-compose.ejs   # AI 활용 사례 일반 글쓰기(TOAST UI WYSIWYG 한 칸, 1~4번 STAR는 편집기 안 템플릿, 제출 시 4필드로 분리·`sanitize-use-case-body` 정제)
│  ├─ ai-cases-write.ejs      # AI 활용 사례 관리자 등록·편집
│  ├─ ax-apply.ejs            # AX 과제 신청
│  ├─ lecture-youtube.ejs     # 유튜브 강의 상세 (iframe 임베드)
│  ├─ lecture-ppt.ejs         # PPT 강의 상세 (슬라이드 이미지)
│  └─ admin-thumbnail-events.ejs  # 썸네일 이벤트 로그 관리자 페이지
├─ config/
│  ├─ company-prompts.json    # AI 프롬프트 라이브러리 템플릿 (Git 추적)
│  └─ mgmt-perf-default-payload.json  # 경영성과 대시보드 기본 차트 시드 (Git 추적)
├─ data/                      # 런타임 JSON·업로드 메타 (Git 제외, `.gitkeep`만 추적)
│  ├─ lectures.json          # 강의 DB (PG 미사용 시)
│  ├─ ai-success-stories.json / ai-success-stories/
│  ├─ thumbnail-jobs.json    # 썸네일 큐 스냅샷
│  └─ thumbnail-events.json  # 썸네일 이벤트 로그
├─ uploads/                   # 업로드된 .pptx 파일 저장
│  └─ thumbnails/             # 생성된 썸네일 이미지
└─ resources/
   └─ lecture/                # 초기 시드용 샘플 PPT (최초 실행 시 자동 등록)

구조 설명

구분 설명
서버 server.js가 Express 앱, 라우트, Multer 업로드, 썸네일 백그라운드 워커, PostgreSQL 연동을 모두 담당
데이터 저장소 ENABLE_POSTGRES=1이면 PostgreSQL lectures 테이블이 단일 소스, 0이면 data/lectures.json 사용
Git과 데이터 data/, public/resources/ai-success/, public/files/.gitignore로 제외(런타임·업로드 전용). config/company-prompts.json·mgmt-perf-default-payload.json만 기본 템플릿으로 추적
썸네일 비동기 큐 처리, data/thumbnail-jobs.json으로 영속화, data/thumbnail-events.json에 이벤트 기록
EJS 템플릿으로 메인/유튜브/PPT/관리자 페이지 렌더링

서버 배포 (새 머신·처음부터)

아래는 저장소를 처음 받아 운영 서버에 올리는 경우를 가정한 절차입니다. macOS(개발용·소규모 호스팅)와 Linux(일반적인 VPS·온프레미스 서버)를 구분했습니다.

공통 사전 요구사항

항목 설명
Node.js v18 이상 권장 (node -v로 확인)
npm Node와 함께 설치
Git 저장소 클론용
PostgreSQL ENABLE_POSTGRES=1 사용 시 접속 가능한 DB(로컬 설치 또는 원격)
환경 변수 .env.example을 복사해 .env 작성(비밀번호·토큰은 반드시 변경)

PPT 썸네일·슬라이드 이미지는 macOS에서는 qlmanage가 우선 사용되고, 그 외에는 LibreOffice·poppler(pdftoppm) 조합이 필요합니다. 도구가 없으면 텍스트 프리뷰로 동작합니다.


macOS에서 배포 (새 머신)

  1. 도구 설치

    • Node.js: nodejs.org LTS 설치, 또는 brew install node@20
    • Git: Xcode Command Line Tools(xcode-select --install) 또는 brew install git
    • PostgreSQL(로컬 DB를 쓸 때): brew install postgresql@16 후 서비스 기동, 또는 원격 DB만 사용
    • PPT 변환 강화(선택): brew install --cask libreoffice, brew install poppler
  2. 코드 받기

    mkdir -p ~/workspace && cd ~/workspace
    git clone <저장소 URL> ai_platform
    cd ai_platform
    

    (이미 클론한 경우 예: cd /Users/dsyoon/workspace/ai_platform)

  3. 의존성·환경

    npm install
    cp .env.example .env
    # 편집기로 .env 수정: PORT, ADMIN_TOKEN, DB_*, ENABLE_POSTGRES 등
    
  4. DB 스키마 (ENABLE_POSTGRES=1일 때)

    npm run db:schema
    
  5. 실행 방식 선택

    • 포그라운드(테스트): npm start → 터미널에 Server started: http://localhost:8030 확인 후 브라우저 접속
    • 백그라운드(PM2): 아래 「PM2로 실행」 절 참고 (pm2 start … --name ai_platform)
  6. 접속
    같은 Mac에서: http://127.0.0.1:8030 (또는 .envPORT). 다른 기기에서 접속하려면 HOST=0.0.0.0이 기본이므로, macOS 방화벽에서 Node 허용 여부를 확인합니다.


Linux에서 배포 (새 서버)

배포 경로는 예시로 /var/www/ai_platform을 둡니다. 배포 사용자·그룹(www-data 등)은 배포 정책에 맞게 조정하세요.

  1. 시스템 패키지 (Ubuntu/Debian 예시)

    sudo apt update
    sudo apt install -y git build-essential
    
    • Node.js: 배포판 기본 패키지가 오래된 경우가 많으므로 NodeSource 또는 nvm으로 v18+ 설치를 권장합니다.
    • PostgreSQL 클라이언트/서버: 원격 DB만 쓰면 클라이언트 라이브러리만으로도 되고, 로컬 DB면 postgresql 패키지 설치 후 DB·사용자 생성.
    • PPT 변환(선택): sudo apt install -y libreoffice poppler-utils
    • PPT 슬라이드 이미지 한글(□·토푸): 변환은 서버에서 이루어지므로 한글 글꼴이 없으면 PNG에만 한글이 깨집니다(HTML UI 한글은 정상일 수 있음). 예: sudo apt install -y fonts-nanum fonts-noto-cjk, 이후 sudo fc-cache -fv 로 fontconfig 갱신 → 관리자 화면에서 해당 강의 슬라이드 이미지 재생성.
  2. 코드 배치

    sudo mkdir -p /var/www
    cd /var/www
    sudo git clone <저장소 URL> ai_platform
    cd ai_platform
    sudo chown -R "$USER:$USER" /var/www/ai_platform   # 이후 작업 사용자에 맞게 조정
    
  3. 의존성·환경

    npm install --production
    cp .env.example .env
    nano .env   # 또는 vim — PORT, ADMIN_TOKEN, DB_*, ENABLE_POSTGRES 등
    
  4. DB 스키마 (ENABLE_POSTGRES=1일 때)

    npm run db:schema
    
  5. 프로세스 관리 (PM2 권장)

    sudo npm install -g pm2
    cd /var/www/ai_platform
    pm2 start server.js --name ai_platform
    pm2 save
    pm2 startup    # 부팅 시 자동 기동(출력되는 sudo 명령 실행)
    
  6. 방화벽·리버스 프록시

    • 외부에 직접 8030을 열지 않고 Apache2(또는 동급 리버스 프록시)로 TLS·역방향 프록시하는 구성이 일반적입니다.
    • Apache2 예시(모듈·VirtualHost·LimitRequestBody·ProxyTimeout)는 docs/DEPLOYMENT-xavis.ncue.net.mddeploy/apache-ai.ncue.net-ssl.conf.example를 참고하세요.
    • ufw 사용 시: sudo ufw allow 80,443/tcp 후 앱은 로컬에서만 듣게 하거나, 프록시 뒤에 둡니다.
  7. 데이터 디렉터리 권한
    data/, uploads/ 등에 앱 실행 사용자(예: pm2로 띄운 사용자, 또는 www-data)가 쓰기 가능해야 합니다. 실패 시 로그에 Failed to bootstrap 또는 권한 오류가 납니다.


배포 후 확인

  • 터미널 또는 pm2 logs ai_platform에서 에러 없이 Server started 로그 확인
  • 브라우저로 메인·/learning·관리자(토큰)까지 동작 확인
  • PostgreSQL 사용 시 npm run db:schema최초 1회(또는 스키마 변경 시); 일일 DB 백업은 아래 「PostgreSQL 백업 및 복원」의 cron 절을 따릅니다.

실행 방법

로컬에서 빠르게 띄우기는 아래 순서면 됩니다. 새 서버에 맞춰 처음부터 배포할 때는 위 「서버 배포 (새 머신·처음부터)」의 macOS 또는 Linux 절을 따르세요.

1) 의존성 설치

npm install

2) PostgreSQL 스키마 적용

npm run db:schema

3) 서버 실행

프로젝트 루트에서:

npm start

정상 기동 시 터미널에 Server started: http://localhost:8030 (또는 설정한 PORT)가 출력됩니다. 이 상태에서만 브라우저로 접속할 수 있습니다.

PM2로 실행

프로젝트 루트에서 실행해야 server.js가 같은 디렉터리의 .env를 읽습니다( dotenv 기준).

1) PM2 설치 (전역, 한 번)

npm install -g pm2

2) 프로젝트 디렉터리로 이동 후 의존성

cd /Users/dsyoon/workspace/ai_platform
npm install
cp .env.example .env   # 최초 1회 — 값 편집

3) 앱 기동

pm2 start server.js --name ai_platform

npm start와 동일하게 server.js를 띄웁니다. 이름만 PM2에서 ai_platform으로 관리합니다.

4) 재부팅 후에도 유지 (선택)

pm2 save
pm2 startup

pm2 startup이 출력하는 sudo env PATH=... 한 줄을 그대로 실행한 뒤, 다시 pm2 save를 하면 부팅 시 자동 기동에 맞춰집니다.

5) 자주 쓰는 명령

목적 명령
상태 확인 pm2 list
로그(실시간) pm2 logs ai_platform
재시작 pm2 restart ai_platform
중지 pm2 stop ai_platform
목록에서 제거 pm2 delete ai_platform

환경 변수: 포트·DB 등은 프로젝트 루트의 .env에 두고, 변경 후 pm2 restart ai_platform으로 반영합니다. 별도 경로에 두었다면 해당 디렉터리에서 pm2 start 하거나, ecosystem 설정으로 cwd를 지정하세요.

운영/배포 환경에서 이미 PM2로 띄운 경우 재시작 예:

pm2 restart ai_platform

로컬에서 포그라운드로만 확인할 때:

node server.js

포트·수신 주소 변경

PORT=8030 npm start
# 로컬 루프백만 (외부 네트워크 인터페이스에 바인딩하지 않음)
HOST=127.0.0.1 npm start

서버는 기본적으로 HOST=0.0.0.0으로 바인딩합니다(동일 기기의 localhost 접속에 사용).

기존 서버 프로세스 종료

터미널을 닫았는데도 이전에 실행한 node server.js가 남아 있거나, **포트가 이미 사용 중(EADDRINUSE)**이라 새로 npm start가 실패할 때, 기존 프로세스를 먼저 종료합니다.

포트로 PID 확인 후 종료 (macOS / Linux, 기본 포트 8030 예시)

lsof -i :8030

출력의 PID 열 값을 확인한 뒤:

kill PID

응답이 없으면 kill -9 PID로 강제 종료할 수 있으나, 다른 Node 작업이 같은 포트를 쓰는지 확인한 뒤 사용하세요.

프로젝트 진입점만 대상으로 종료 (다른 node 작업에 영향을 줄 수 있으니 주의)

pkill -f "node server.js"

PM2로 띄운 경우

pm2 list
pm2 stop ai_platform
# 완전히 제거하려면
pm2 delete ai_platform

Windows에서는 작업 관리자에서 Node.js 프로세스를 종료하거나, PowerShell에서 Get-NetTCPConnection -LocalPort 8030 등으로 점유 프로세스를 확인한 뒤 해당 PID를 종료합니다.

관리자 토큰/페이지 크기 설정(선택)

ADMIN_TOKEN=my-secret PAGE_SIZE=12 npm start
  • ADMIN_TOKEN 미지정 시 기본값: ncue-admin
  • PAGE_SIZE 미지정 시 기본값: 8
  • .env 파일이 있으면 dotenv로 자동 로드
  • 브라우저에서는 좌측 메뉴 하단 관리자 → 모달에 위 토큰을 입력해 세션 쿠키를 발급받는 방식으로 /admin에 진입할 수 있습니다(임직원 이메일 로그인 여부와 동일한 흐름).

PostgreSQL 연결 설정

  • ENABLE_POSTGRES=1일 때: PostgreSQL이 단일 소스로 사용됩니다. data/lectures.json은 사용하지 않습니다.
  • ENABLE_POSTGRES=0일 때: data/lectures.json만 사용합니다.
  • DB 연결 실패 시 자동으로 data/lectures.json 기반 파일 저장소로 폴백합니다.
  • 필수 변수: DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD

PostgreSQL 백업 및 복원

운영 서버에서 매일 cron으로 PostgreSQL 서버의 연결 가능 DB 전체를 백업하고, 장애·실수 삭제 시 복원하는 절차입니다.
기본(PG_BACKUP_SCOPE=all)은 템플릿 DB를 제외한 모든 데이터베이스를 DB마다 {dbname}.dump 파일로 저장합니다. 역할(계정)은 PG_BACKUP_GLOBALS=1과 슈퍼유저 비밀번호가 있으면 00_globals.sql로 함께 백업합니다.

개요

항목 내용
백업 범위(기본) PostgreSQL 서버의 모든 DB (template0/template1 제외)
백업 방식 DB별 pg_dump -Fc + (선택) pg_dumpall --globals-only
스크립트 scripts/pg-backup.sh, scripts/pg-restore.sh
npm npm run db:backup, npm run db:restore -- <옵션> <파일 또는 디렉터리>
설정 프로젝트 루트 .envDB_*, PG_BACKUP_*
저장 위치(기본) /home/xavis/workspace/backup/ai_platform/YYYYMMDD/
최신 심볼릭 링크 /home/xavis/workspace/backup/ai_platform/latest → 가장 최근 백업 디렉터리

백업 파일에는 각 DB의 테이블·데이터·인덱스·시퀀스와(옵션) 역할·권한이 포함됩니다. Git·공개 저장소에 올리지 마세요.

사전 요구사항

  1. PostgreSQL 클라이언트 도구 (pg_dump, pg_restore, psql)가 서버 PATH에 있어야 합니다.
    • Ubuntu/Debian: sudo apt install -y postgresql-client
    • macOS: brew install libpq 후 PATH에 pg_dump 추가
  2. **.env**에 DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD가 앱과 동일하게 설정되어 있어야 합니다.
  3. 전체 DB 백업을 위해서는 .envPG_BACKUP_SUPERUSER_PASSWORD(postgres 등 슈퍼유저)를 설정하는 것을 권장합니다. 없으면 DB_USERNAME이 접근 가능한 DB만 백업됩니다.
  4. 디스크 여유: (모든 DB 크기 합) × 보관 일수(30) × 1.2 이상 권장.

환경 변수 (.env)

변수 필수 기본값 설명
DB_HOST O PostgreSQL 호스트
DB_PORT 5432 포트
DB_DATABASE O 앱 DB 이름(복원·단일 백업 시 참고). PG_BACKUP_SCOPE=single일 때만 백업 대상
DB_USERNAME O 앱 DB 계정
DB_PASSWORD O 앱 DB 비밀번호
PG_BACKUP_DIR /home/xavis/workspace/backup/ai_platform 백업 루트 디렉터리
PG_BACKUP_RETENTION_DAYS 30 이 일수보다 오래된 날짜 폴더(YYYYMMDD) 삭제. 0이면 삭제 안 함
PG_BACKUP_SCOPE all all=서버의 모든 DB, single=DB_DATABASE
PG_BACKUP_GLOBALS 1 1이면 역할(계정)·권한 SQL(00_globals.sql)도 함께 백업
PG_BACKUP_SUPERUSER postgres globals 백업·전체 DB dump 시 사용
PG_BACKUP_SUPERUSER_PASSWORD 전체 백업 권장 슈퍼유저 비밀번호

운영 서버 .env 예시 (백업 관련만):

PG_BACKUP_DIR=/home/xavis/workspace/backup/ai_platform
PG_BACKUP_RETENTION_DAYS=30
PG_BACKUP_SCOPE=all
PG_BACKUP_GLOBALS=1
PG_BACKUP_SUPERUSER=postgres
PG_BACKUP_SUPERUSER_PASSWORD=실제_슈퍼유저_비밀번호

PG_BACKUP_SCOPE=single로 바꾸면 예전처럼 DB_DATABASE 하나만 백업합니다.

수동 백업

프로젝트 루트에서:

npm run db:backup
# 또는
bash scripts/pg-backup.sh

성공 시 예시 출력:

[2026-05-25T02:00:01+09:00] pg-backup start → /home/xavis/workspace/backup/ai_platform/20260525 (scope=all, retention 30 days)
[2026-05-25T02:00:02+09:00] globals saved: .../20260525/00_globals.sql
[2026-05-25T02:00:03+09:00] dumping database: ai_web_platform
[2026-05-25T02:00:15+09:00] dump saved: .../20260525/ai_web_platform.dump (12345678 bytes)
[2026-05-25T02:00:16+09:00] dumping database: postgres
[2026-05-25T02:00:17+09:00] dump saved: .../20260525/postgres.dump (456789 bytes)
[2026-05-25T02:00:17+09:00] retention: pruned 1 dir(s); keeping backups from 20260425 onward (30 days)
[2026-05-25T02:00:17+09:00] pg-backup done: 2 database(s), latest → .../latest

생성 파일:

/home/xavis/workspace/backup/ai_platform/
├─ 20260525/
│  ├─ 00_globals.sql            # 역할·권한 (PG_BACKUP_GLOBALS=1)
│  ├─ 00_manifest.txt           # 백업 DB 목록
│  ├─ ai_web_platform.dump
│  ├─ postgres.dump
│  └─ …                         # 서버의 다른 DB마다 *.dump
└─ latest -> 20260525/          # 심볼릭 링크 (최대 30일치 폴더 유지)

매일 cron 등록 (운영 서버)

  1. 백업 디렉터리 생성 및 권한:
sudo mkdir -p /home/xavis/workspace/backup/ai_platform
sudo chown "$(whoami):$(whoami)" /home/xavis/workspace/backup/ai_platform
# PM2/앱과 같은 사용자로 cron을 돌릴 경우 그 사용자로 chown
  1. .envPG_BACKUP_DIR=/home/xavis/workspace/backup/ai_platform 설정.

  2. crontab 편집:

crontab -e
  1. 매일 새벽 2시 (프로젝트 경로는 배포 위치에 맞게 수정):
0 2 * * * cd /home/xavis/workspace/ai_platform && /usr/bin/bash scripts/pg-backup.sh >> /var/log/pg-backup.log 2>&1
  1. 로그 로테이션(선택): /var/log/pg-backup.log가 커지지 않도록 logrotate 또는 주기적 truncate.

cron 점검

# 다음 실행 예약 확인
crontab -l

# 수동 1회 실행 후 로그 확인
cd /home/xavis/workspace/ai_platform && bash scripts/pg-backup.sh
tail -20 /var/log/pg-backup.log

# 최신 dump 목록 확인
ls -lh /home/xavis/workspace/backup/ai_platform/latest/
cat /home/xavis/workspace/backup/ai_platform/latest/00_manifest.txt

복원

.dump 파일은 custom format 전용입니다. plain SQL(.sql)이 아니면 psql -f가 아니라 pg_restore를 사용합니다.

1) 검증용 복원 — DB 하나

npm run db:restore -- --test --confirm /home/xavis/workspace/backup/ai_platform/latest/ai_web_platform.dump

1-2) 검증용 복원 — 서버 DB 전체

npm run db:restore -- --all --globals --test --confirm /home/xavis/workspace/backup/ai_platform/latest/

각 DB는 {dbname}_restore_test 이름으로 복원됩니다(예: ai_web_platform_restore_test).

확인:

psql -h "$DB_HOST" -U "$DB_USERNAME" -d ai_web_platform_restore_test -c "\dt"
psql -h "$DB_HOST" -U "$DB_USERNAME" -d ai_web_platform_restore_test \
  -c "SELECT relname, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC LIMIT 10;"

검증 후 테스트 DB 삭제:

psql -h "$DB_HOST" -U postgres -c 'DROP DATABASE ai_web_platform_restore_test;'

(DROP은 슈퍼유저 또는 DB owner 권한 필요)

2) 운영 DB 복원 — DB 하나 (주의)

pm2 stop webplatform   # 앱 이름에 맞게

bash scripts/pg-restore.sh --clean --confirm /home/xavis/workspace/backup/ai_platform/latest/ai_web_platform.dump

pm2 start webplatform

2-2) 운영 DB 복원 — 서버 DB 전체 (주의)

pm2 stop webplatform

bash scripts/pg-restore.sh --all --globals --clean --confirm /home/xavis/workspace/backup/ai_platform/latest/

pm2 start webplatform
  • --clean: 기존 객체를 삭제한 뒤 dump 내용으로 재생성 (--if-exists 포함).
  • 운영 DB에 --clean 없이 복원하면 중복 객체 오류가 날 수 있습니다.
  • --confirm 없이 실행하면 대화형 확인 프롬프트가 뜹니다.

역할(계정)까지 함께 백업해 둔 경우(00_globals.sql):

bash scripts/pg-restore.sh --globals --clean --confirm /home/xavis/workspace/backup/ai_platform/.../ai_web_platform.dump

.envPG_BACKUP_SUPERUSER, PG_BACKUP_SUPERUSER_PASSWORD 필요.

3) 완전히 새 DB로 복원 (드물게)

DB 자체를 drop/create한 뒤 복원할 때는 DBA·슈퍼유저로:

psql -h "$DB_HOST" -U postgres -c "DROP DATABASE IF EXISTS ai_web_platform;"
psql -h "$DB_HOST" -U postgres -c "CREATE DATABASE ai_web_platform OWNER xavis;"
bash scripts/pg-restore.sh --confirm /home/xavis/workspace/backup/ai_platform/.../ai_web_platform.dump

백업·복원 체크리스트 (월 1회 권장)

Check 방법
cron 정상 실행 /var/log/pg-backup.log에 전일 pg-backup done 기록
dump 파일 크기 00_manifest.txt·각 .dump가 0 byte가 아닌지 확인
복원 테스트 --all --test 또는 단일 --test_restore_test DB 확인
디스크 df -h $PG_BACKUP_DIR
보관 정책 PG_BACKUP_RETENTION_DAYS에 맞게 오래된 폴더 삭제되는지

보안

  • dump 파일은 개인정보·업무 데이터 전체를 담습니다. 권한 600/700 디렉터리, Git·Slack·메일 첨부 금지.
  • 가능하면 백업 저장소를 앱 서버와 분리(NAS, 객체 스토리지)하고 주기적으로 오프사이트 복사.
  • DB는 공인 IP 직접 노출 대신 방화벽·VPN·SSH 터널 사용(별도 보안 가이드 참고).

문제 해결

증상 조치
pg_dump: command not found postgresql-client 설치
password authentication failed .envDB_USERNAME/DB_PASSWORD 확인
could not connect to server DB_HOST·방화벽·PostgreSQL listen_addresses
cron은 도는데 파일 없음 cron의 cd 경로, .env 존재, 로그 파일 stderr 확인
pg_restore: error: role "xxx" does not exist --no-owner --role=DB_USERNAME(스크립트 기본) 또는 --globals로 역할 먼저 복원
복원 후 앱 오류 pm2 restart, npm run db:schema복원 dump에 스키마 포함 시 보통 불필요

data/ JSON과의 관계

ENABLE_POSTGRES=1이면 강의·회의록·경영성과 등 핵심 데이터는 PostgreSQL이 단일 소스입니다.
data/ai-success-stories.json, 업로드 파일(uploads/, public/resources/) 등 파일 기반 데이터는 DB 백업에 포함되지 않습니다. 운영 백업 정책에 파일 디렉터리 rsync/tar 백업을 별도 두는 것을 권장합니다.


  • 인앱 채팅 화면은 /ai-explore/chat에서 제공하며, 백엔드는 POST /api/chat/stream SSE로 응답을 생성합니다.
  • 기본 제공 모델 별칭은 gpt-5.4, gpt-5-mini이며, 실제 API 모델 ID는 .envOPENAI_MODEL_DEFAULT, OPENAI_MODEL_MINI로 매핑합니다.
  • OPENAI_WEB_SEARCH=1(기본)일 때 OpenAI Responses API의 웹 검색 도구를 사용해 출처 링크를 함께 내려줍니다.
  • 필수 환경 변수는 OPENAI_API_KEY입니다.

PPT 썸네일 및 슬라이드 이미지 생성 제어(선택)

ENABLE_PPT_THUMBNAIL=1 npm start
  • 기본값: 1 (활성)
  • ENABLE_PPT_THUMBNAIL=0이면 썸네일·슬라이드 이미지 생성 비활성화
  • 우선순위: macOS qlmanage → LibreOffice(soffice/libreoffice) + pdftoppm
  • 도구 미설치 또는 변환 실패 시 텍스트 프리뷰로 자동 폴백

PPT 슬라이드 이미지(뷰어용): PPTX 파일은 LibreOffice로 PDF 변환 후 pdftoppm으로 이미지 생성.

  • PPTX: LibreOffice 필수. macOS: brew install --cask libreoffice
  • PDF: pdftoppm만 있으면 동작 (poppler 패키지)
  • 한글 깨짐(이미지 안만 □): 서버에 PPT가 쓰는 글꼴·한글 폰트가 없을 때 발생. Linux: fonts-nanum, fonts-noto-cjk 등 설치 후 fc-cache -fv, 슬라이드 이미지 재생성.

업로드 동영상 카드 썸네일(선택)

  • 기본값: ENABLE_VIDEO_THUMBNAIL0이 아니면 활성(미설정 시 켜짐).
  • 서버 PATHffmpeg가 있어야 합니다. macOS: brew install ffmpeg, Ubuntu: sudo apt install -y ffmpeg.
  • 추출 시각은 VIDEO_THUMB_SEEK_SEC(초, 기본 0.5)로 조정할 수 있습니다. 영상 앞부분이 검은 화면이면 값을 키워 보세요.
  • ENABLE_VIDEO_THUMBNAIL=0이면 업로드 직후 썸네일은 생성하지 않으며, 카드는 기존처럼 텍스트 폴백만 표시합니다.

.env 예시

PORT=8030
HOST=0.0.0.0
ADMIN_TOKEN=ncue-admin
PAGE_SIZE=8
ENABLE_POSTGRES=1
DB_HOST=your-db-host
DB_PORT=5432
DB_DATABASE=your_database
DB_USERNAME=your_user
DB_PASSWORD=your_password
ENABLE_PPT_THUMBNAIL=1
ENABLE_VIDEO_THUMBNAIL=1
# VIDEO_THUMB_SEEK_SEC=0.5
THUMBNAIL_WIDTH=1000
THUMBNAIL_MAX_RETRY=2
THUMBNAIL_RETRY_DELAY_MS=5000
THUMBNAIL_EVENT_KEEP=200
THUMBNAIL_EVENT_PAGE_SIZE=50

# 채팅 기능 (OpenAI API 키)
OPENAI_API_KEY=
# OPENAI_MODEL_DEFAULT=gpt-4o
# OPENAI_MODEL_MINI=gpt-4o-mini
# OPS 이메일(@ncue.net) 로그인 세션: `OPS_SESSION_TTL_DAYS` 미설정·0·never = 만기 없음(이전 기본 15일). N(양의 정수)이면 로그인일+N일(서울 달력) 후 만료.
# OPS_SESSION_TTL_DAYS=0

사용 방법

  1. 메인 페이지에서 강의 등록
    • 유튜브 강의 등록: 제목 + 유튜브 링크 (+설명)
    • PowerPoint 강의 등록: 제목 + .pptx 파일 (+설명)
    • 동영상 파일 등록: 제목 + 영상 파일 (ffmpeg로 카드 썸네일 생성)
    • 두 등록 폼 모두 태그(쉼표 구분) 입력 가능
  2. 하단 등록된 강의 카드에서 항목 클릭
  3. 강의 상세 화면에서 시청
    • 유튜브: 동영상 재생
    • PPT: 슬라이드 이미지 확인(미생성 장은 이미지 없이 카드만 표시)

검색/필터

  • 검색어, 타입, 태그를 조합해서 목록을 조회할 수 있습니다.
  • 결과 목록은 페이지 단위로 분할되어 이동 가능합니다.

관리자 삭제

  1. 화면의 관리자 모드에서 토큰 입력 후 활성화
  2. 강의 카드의 삭제 버튼으로 즉시 삭제
  3. PPT 강의 삭제 시 업로드 파일도 함께 제거 시도

썸네일 재생성(관리자)

  • 관리자 모드에서 PPT·업로드 동영상 카드의 썸네일 재생성 버튼으로 수동 재생성 가능
  • 실패 썸네일 일괄 재시도 버튼으로 실패 건을 큐에 일괄 재등록 가능
  • 이벤트 로그 페이지에서 기간/유형/강의ID/사유 필터 조회 가능
  • 이벤트 로그는 CSV 다운로드 지원

주요 라우트

메뉴별 화면

경로 설명
GET / 학습센터 뷰어 (강의 목록)
GET /learning 학습센터 뷰어 (검색/필터/페이지네이션)
GET /admin 관리자 대시보드 (강의·썸네일·AI 등)
GET /chat 회사규정 NotebookLM 페이지로 리다이렉트
GET /wm WM NotebookLM 페이지로 리다이렉트
GET /ai-explore/chat OpenAI 연동 채팅 화면
GET /ai-explore/fscan AI 플랫폼 내 FSCAN 조사각 선정도우미 화면(내재화 iframe)
GET /ai-explore AI 탐색 (회의록·체크리스트 등 카드; 프롬프트 라이브러리는 /ai-explore/prompts 또는 좌측 프롬프트 메뉴)
GET /ai-explore/prompts 프롬프트 라이브러리 — 공식 템플릿(config/company-prompts.json)+팀 공유·좋아요(PostgreSQL prompt_community_entries·prompt_likes) · ?id= 딥링크
GET /ai-cases AI 활용 사례 목록(검색·태그 필터) — 관리자 등록(ai-success-stories 메타) + 일반 제출(ai_use_case_submissions) 병합
GET /ai-cases/submit/:id 일반 제출 사례 상세(UUID) — 본인·관리자에게 상단 수정하기 링크(GET /ai-cases/compose?edit=<uuid>)
GET /ai-cases/:slug AI 활용 사례 상세(메타·슬러그)
GET /ai-cases/write AI 활용 사례 관리(관리자 토큰 필요)
POST/PUT/DELETE /api/ai-success-stories 사례 CRUD(관리자) · 본문은 data/ai-success-stories/*.md
GET /ai-cases/compose?edit=:uuid 일반 제출 수정 화면(제목·4섹션·태그·첨부·썸네일) — 권한 없으면 403
POST /api/ai-use-case-submissions AI 활용 사례 일반 제출(글쓰기) — OPS 이메일 로그인·PostgreSQL, ai_use_case_submissions — 본문 4필드는 WYSIWYG HTML이며 sanitize-html로 안전한 태그만 저장, 글자 수는 보이는 텍스트(태그 제외) 합산
PUT /api/ai-use-case-submissions/:id 일반 제출 갱신(제출자 이메일과 로그인 이메일 일치 또는 관리자 모드). 썸네일은 새 파일이면 교체, removeThumbnail=1이면 삭제(새 파일·삭제 둘 다 없으면 유지). 첨부는 기존을 removeAttachmentPaths(JSON 경로 배열)로 뺀 뒤, 새로 업로드한 파일이 뒤에 추가(합계 최대 10개)
GET /ax-apply AX 과제 신청
GET /lectures/:id 강의 상세 뷰어 (유튜브/PPT)

AI 활용 사례 목록이 줄어든 경우: 카드 목록은 **data/ai-success-stories.json**만 본다. git pull 등으로 JSON만 예전 버전이 되면 .md·PDF는 서버에 남아 있어도 1건만 보일 수 있다. 서버에서 data/ai-success-stories/*.md는 있는데 JSON에 없으면 node scripts/merge-orphan-ai-success-stories.js로 고아 .md를 메타에 다시 붙인 뒤, 관리자 화면에서 부서·저자·PDF 경로 등을 점검한다. JSON·본문·public/resources/ai-success/ PDF는 배포 서버에서 백업하거나 저장소에 포함하는 정책을 권장한다.

API

  • POST /api/prompts/likes/toggle (OPS·DEV 이메일)
    JSON { "kind": "official" | "community", "id": "<json id or uuid>" } — 좋아요 토글. { ok, liked, likeCount }

  • POST /api/prompts/community (OPS·DEV 이메일)
    multipart/form-datatitle, body, description?, tag?, promptFiles(05), resultFiles(05) · 작성자는 요청한 OPS 로그인 이메일이 prompt_community_entries.author_email에 저장됨 · DB prompt_attachments / result_sample_attachments JSON · 파일당 20MB → { ok, id, createdAt, promptFiles, resultFiles } · 라이브러리 카드에는 이메일의 @ 앞부분(예: spark_ai@ncue.netspark_ai)이 표시됨

  • DELETE /api/prompts/community/:id (OPS·DEV 이메일) — 본인 글만 소프트 삭제

  • POST /api/prompts/polish-workflow (OPS·DEV 이메일)
    JSON { draft }OPENAI_API_KEY로 워크플로 초안을 다듬은 프롬프트 문자열 { text } 반환

  • GET /api/chat/config
    채팅용 OpenAI 키 설정 여부 { configured: boolean } (키 문자열은 노출하지 않음)

  • POST /api/chat
    JSON { model, messages } — OpenAI Chat Completions 연동(비스트리밍). OPENAI_API_KEY 필수.

  • POST /api/chat/stream
    동일 본문으로 SSE(text/event-stream) 스트리밍 응답. 이벤트: data: {"type":"delta","text":"..."} 조각, 마지막 {"type":"done"}. 오류 시 {"type":"error","error":"..."}. 채팅 UI는 이 엔드포인트를 사용합니다.

  • POST /lectures/youtube
    유튜브 강의 등록

  • POST /lectures/ppt
    PPT 강의 등록 (multipart/form-data)

  • POST /lectures/:id/delete
    관리자 토큰 기반 강의 삭제

  • POST /lectures/:id/thumbnail/regenerate
    관리자 토큰 기반 PDF/PPT·업로드 동영상 썸네일 재생성

  • POST /thumbnails/retry-failed
    관리자 토큰 기반 실패 썸네일 일괄 재시도 큐 등록

  • GET /api/queue/metrics?token=...
    관리자 토큰 기반 큐/상태 메트릭 JSON 조회

  • GET /api/queue/events-summary?token=...&hours=24
    최근 시간대 요약 KPI/시간대별 처리량 JSON 조회

  • GET /admin/thumbnail-events?token=...
    관리자 이벤트 로그 페이지 (필터/페이지네이션)

  • GET /admin/thumbnail-events.csv?token=...
    필터 기준 이벤트 로그 CSV 다운로드


최근 업데이트 (요약)

  • PostgreSQL 백업·복원: scripts/pg-backup.sh / scripts/pg-restore.sh, npm run db:backup · README 「PostgreSQL 백업 및 복원」에 cron·복원 절차 문서화
  • 채팅 (/ai-explore/chat): OpenAI SSE 기반 인앱 채팅으로 응답/출처를 표시
  • AI 탐색 필터 (/ai-explore): 검색창 아래 타입 라디오 필터(일반/XScan/FScan)로 카드 목록 필터링
  • FSCAN 도구 내재화: public/resources/fscan/fscan-selector-v1.html 정적 페이지를 /ai-explore/fscan에서 iframe으로 감싸 AI 플랫폼 레이아웃(좌측 메뉴/뒤로 이동) 안에서 사용
  • AI 탐색 (/ai-explore): 메인 콘텐츠를 뷰포트 전체 너비로 사용(회의록·체크리스트 등). 프롬프트 라이브러리는 AI 목록이 아닌 좌측 프롬프트 메뉴(/ai-explore/prompts)로 진입. 좌측 전체 메뉴는 관리자 여부와 관계없이 동일하게 표시·접근 가능
  • 프롬프트 라이브러리 (/ai-explore/prompts): 시나리오 카드·미리보기·복사 UI, 템플릿 데이터는 config/company-prompts.json에서 로드
  • 서버 수신 주소: 기본 HOST=0.0.0.0(로컬 localhost 접속에 사용). 필요 시 HOST=127.0.0.1로 제한 가능
  • 문서: localhost 접속 불가 시 확인할 항목을 아래 「접속이 안 될 때 (트러블슈팅)」 절에 정리

기술 스택

  • Node.js
  • Express
  • EJS
  • PostgreSQL (pg)
  • Multer (파일 업로드)
  • JSZip (.pptx 내부 XML 파싱)
  • UUID

구현 참고/제약

  • PPT 뷰어는 원본 슬라이드 디자인 렌더링이 아닌, .pptx 내부 텍스트를 추출해 보여주는 방식입니다.
  • PPT·동영상 썸네일은 시스템 도구(qlmanage/LibreOffice/ffmpeg 등) 상태에 따라 생성 실패할 수 있으며, 이 경우 이미지 없이 텍스트 프리뷰만 표시됩니다.
  • 썸네일 큐는 단일 프로세스 워커 기준으로 동작합니다(다중 인스턴스 분산 락은 미구현).
  • 폰트/도형/애니메이션까지 완전 동일 렌더링이 필요하면 별도 문서 렌더러(예: LibreOffice/PDF 변환 파이프라인) 연동이 필요합니다.
  • 이벤트 로그 페이지의 실시간 그래프는 클라이언트 폴링 기반이며, 다수 접속 시 폴링 주기 조정이 필요할 수 있습니다.