xavis 소스·DB 스키마·활용사례/F-Scan/프롬프트 라이브러리 등 기능 반영. @xavis.co.kr → @ncue.net, 관리자 토큰 ncue-admin, 런타임 data/ Git 추적 제외. Co-authored-by: Cursor <cursoragent@cursor.com>
51 KiB
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 start → http://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}
접속이 안 될 때 (트러블슈팅)
- 서버가 떠 있는지 확인
프로젝트 루트에서npm start를 실행하고, 터미널에Server started: http://localhost:8030같은 로그가 나오는지 확인합니다. - 포트 번호 확인
.env에PORT=...가 있으면 그 포트로 접속합니다. (기본값은8030) - 부팅 실패
터미널에Failed to bootstrap:이 나오면 프로세스가 종료되며 HTTP 서버가 뜨지 않습니다. 메시지를 확인한 뒤data/쓰기 권한, 디스크 여유, DB 설정 등을 점검합니다. - 포트 충돌
다른 프로그램이 8030을 쓰는 경우PORT=8031 npm start처럼 바꿔 실행하거나, 이미 떠 있는 Node 서버 프로세스를 종료합니다. 구체적인 명령은 아래 실행 방법 절의 기존 서버 프로세스 종료 항목을 참고하세요. - 바인드 주소
기본은 모든 네트워크 인터페이스(0.0.0.0)에서 수신합니다. 로컬 루프백만 쓰려면HOST=127.0.0.1 npm start를 사용할 수 있습니다.
배포 서버: git pull이 data/ 런타임 파일 때문에 막힐 때
원격이 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.js의buildMeetingMinutesSystemPrompt에서 구성하며, 기본 구조 중 「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) 요청 시 **XHRupload.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열 등)로 깨진 액션 표를 교정하고, 「담당」열에 들어 간 발언자·저희·우리 팀 등 통칭·역할 명칭 단독은 규칙 후처리 시 미정으로 바꿀 수 있습니다. DBmeeting_ai_prompts.include_checklist가true일 때만 회의 체크리스트 강제 블록을 넣고, 기본값은false입니다.include_checklist가false이면 생성 직후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(...))우선, 이어서Bufferlatin1→utf8). Busboy에defParamCharset: 'utf8'를 켜면 이중 디코딩으로 깨질 수 있어 두지 않습니다. 탭 전환·차트 렌더는 ASCII 섹션 id(mgmt-sec-sales등)와state.currentSection으로 동기화합니다. 리버스 프록시로 Apache2를 쓰는 경우 업로드 실패·413이면 VirtualHostLimitRequestBody(예: 바이트 단위로deploy/apache-ai.ncue.net-ssl.conf.example와 맞출 것)와/api/·/mgmt-perf/→ NodeProxyPass전달 여부를 확인. (Nginx를 쓰는 환경이면client_max_body_size등을 해당 서버 설정에 맞춥니다.) 엑셀 집계 치환은npm install로xlsx설치 후 서버 재시작. - 경영성과 데이터 확인: 브라우저에서
GET /api/mgmt-perf/status(JSON)로 최근 스냅샷의payloadKeys,_uploadMeta(행 수 등)를 확인할 수 있습니다. 현재 구현은 엑셀에서 매출일보 행 수·시트명만payload._uploadMeta에 넣고, 차트 수치는 기본 시드 JSON(config/mgmt-perf-default-payload.json)을 씁니다. 5,000행이어도 차트가 엑셀 집계와 일치하려면 별도 집계·매핑 로직이 필요합니다. - 대시보드 메뉴 접근:
.env의DASHBOARD_MENU_ALLOWED_EMAILS에 쉼표로 구분한 OPS 로그인 이메일만 좌측 대시보드 메뉴·/dashboard·경영성과 API가 보입니다. 목록이 비어 있으면 누구에게도 표시되지 않습니다. 로컬(DEV)에서 관리자 토큰만 쓰는 경우DASHBOARD_MENU_DEV_USE_MEETING_EMAIL=1과MEETING_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에서 배포 (새 머신)
-
도구 설치
- 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
- Node.js: nodejs.org LTS 설치, 또는
-
코드 받기
mkdir -p ~/workspace && cd ~/workspace git clone <저장소 URL> ai_platform cd ai_platform(이미 클론한 경우 예:
cd /Users/dsyoon/workspace/ai_platform) -
의존성·환경
npm install cp .env.example .env # 편집기로 .env 수정: PORT, ADMIN_TOKEN, DB_*, ENABLE_POSTGRES 등 -
DB 스키마 (
ENABLE_POSTGRES=1일 때)npm run db:schema -
실행 방식 선택
- 포그라운드(테스트):
npm start→ 터미널에Server started: http://localhost:8030확인 후 브라우저 접속 - 백그라운드(PM2): 아래 「PM2로 실행」 절 참고 (
pm2 start … --name ai_platform)
- 포그라운드(테스트):
-
접속
같은 Mac에서:http://127.0.0.1:8030(또는.env의PORT). 다른 기기에서 접속하려면HOST=0.0.0.0이 기본이므로, macOS 방화벽에서 Node 허용 여부를 확인합니다.
Linux에서 배포 (새 서버)
배포 경로는 예시로 /var/www/ai_platform을 둡니다. 배포 사용자·그룹(www-data 등)은 배포 정책에 맞게 조정하세요.
-
시스템 패키지 (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 갱신 → 관리자 화면에서 해당 강의 슬라이드 이미지 재생성.
-
코드 배치
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 # 이후 작업 사용자에 맞게 조정 -
의존성·환경
npm install --production cp .env.example .env nano .env # 또는 vim — PORT, ADMIN_TOKEN, DB_*, ENABLE_POSTGRES 등 -
DB 스키마 (
ENABLE_POSTGRES=1일 때)npm run db:schema -
프로세스 관리 (PM2 권장)
sudo npm install -g pm2 cd /var/www/ai_platform pm2 start server.js --name ai_platform pm2 save pm2 startup # 부팅 시 자동 기동(출력되는 sudo 명령 실행) -
방화벽·리버스 프록시
- 외부에 직접
8030을 열지 않고 Apache2(또는 동급 리버스 프록시)로 TLS·역방향 프록시하는 구성이 일반적입니다. - Apache2 예시(모듈·VirtualHost·
LimitRequestBody·ProxyTimeout)는 docs/DEPLOYMENT-xavis.ncue.net.md 및deploy/apache-ai.ncue.net-ssl.conf.example를 참고하세요. ufw사용 시:sudo ufw allow 80,443/tcp후 앱은 로컬에서만 듣게 하거나, 프록시 뒤에 둡니다.
- 외부에 직접
-
데이터 디렉터리 권한
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
- 기본 포트:
8030 - 접속 URL: http://localhost:8030
포트·수신 주소 변경
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-adminPAGE_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 -- <옵션> <파일 또는 디렉터리> |
| 설정 | 프로젝트 루트 .env의 DB_*, PG_BACKUP_* |
| 저장 위치(기본) | /home/xavis/workspace/backup/ai_platform/YYYYMMDD/ |
| 최신 심볼릭 링크 | /home/xavis/workspace/backup/ai_platform/latest → 가장 최근 백업 디렉터리 |
백업 파일에는 각 DB의 테이블·데이터·인덱스·시퀀스와(옵션) 역할·권한이 포함됩니다. Git·공개 저장소에 올리지 마세요.
사전 요구사항
- PostgreSQL 클라이언트 도구 (
pg_dump,pg_restore,psql)가 서버 PATH에 있어야 합니다.- Ubuntu/Debian:
sudo apt install -y postgresql-client - macOS:
brew install libpq후 PATH에pg_dump추가
- Ubuntu/Debian:
- **
.env**에DB_HOST,DB_PORT,DB_DATABASE,DB_USERNAME,DB_PASSWORD가 앱과 동일하게 설정되어 있어야 합니다. - 전체 DB 백업을 위해서는
.env에PG_BACKUP_SUPERUSER_PASSWORD(postgres 등 슈퍼유저)를 설정하는 것을 권장합니다. 없으면DB_USERNAME이 접근 가능한 DB만 백업됩니다. - 디스크 여유: (모든 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 등록 (운영 서버)
- 백업 디렉터리 생성 및 권한:
sudo mkdir -p /home/xavis/workspace/backup/ai_platform
sudo chown "$(whoami):$(whoami)" /home/xavis/workspace/backup/ai_platform
# PM2/앱과 같은 사용자로 cron을 돌릴 경우 그 사용자로 chown
-
.env에PG_BACKUP_DIR=/home/xavis/workspace/backup/ai_platform설정. -
crontab 편집:
crontab -e
- 매일 새벽 2시 (프로젝트 경로는 배포 위치에 맞게 수정):
0 2 * * * cd /home/xavis/workspace/ai_platform && /usr/bin/bash scripts/pg-backup.sh >> /var/log/pg-backup.log 2>&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
.env에 PG_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 |
.env의 DB_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/streamSSE로 응답을 생성합니다. - 기본 제공 모델 별칭은
gpt-5.4,gpt-5-mini이며, 실제 API 모델 ID는.env의OPENAI_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_THUMBNAIL이0이 아니면 활성(미설정 시 켜짐). - 서버 PATH에
ffmpeg가 있어야 합니다. 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
사용 방법
- 메인 페이지에서 강의 등록
- 유튜브 강의 등록: 제목 + 유튜브 링크 (+설명)
- PowerPoint 강의 등록: 제목 +
.pptx파일 (+설명) - 동영상 파일 등록: 제목 + 영상 파일 (
ffmpeg로 카드 썸네일 생성) - 두 등록 폼 모두 태그(쉼표 구분) 입력 가능
- 하단 등록된 강의 카드에서 항목 클릭
- 강의 상세 화면에서 시청
- 유튜브: 동영상 재생
- PPT: 슬라이드 이미지 확인(미생성 장은 이미지 없이 카드만 표시)
검색/필터
- 검색어, 타입, 태그를 조합해서 목록을 조회할 수 있습니다.
- 결과 목록은 페이지 단위로 분할되어 이동 가능합니다.
관리자 삭제
- 화면의 관리자 모드에서 토큰 입력 후 활성화
- 강의 카드의
삭제버튼으로 즉시 삭제 - 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-data—title,body,description?,tag?,promptFiles(05),5) · 작성자는 요청한 OPS 로그인 이메일이resultFiles(0prompt_community_entries.author_email에 저장됨 · DBprompt_attachments/result_sample_attachmentsJSON · 파일당 20MB →{ ok, id, createdAt, promptFiles, resultFiles }· 라이브러리 카드에는 이메일의@앞부분(예:spark_ai@ncue.net→spark_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 변환 파이프라인) 연동이 필요합니다.
- 이벤트 로그 페이지의 실시간 그래프는 클라이언트 폴링 기반이며, 다수 접속 시 폴링 주기 조정이 필요할 수 있습니다.