# Web Platform (학습센터) 유튜브 링크와 PowerPoint(`.pptx`) 강의를 등록하고, 목록에서 클릭해 바로 시청할 수 있는 사내 학습센터 예제입니다. --- ## 접속 방법 **브라우저에서 열기 전에 반드시 터미널에서 서버를 실행해야 합니다.** (`npm start` 미실행 상태에서는 `http://localhost:8030`에 연결되지 않습니다.) 서버 실행 후 아래 주소로 접속합니다. | 환경 | URL | |------|-----| | 로컬 기본 | [http://localhost:8030](http://localhost:8030) | | 포트 변경 시 | `http://localhost:{PORT}` (예: `PORT=4000 npm start` → `http://localhost:4000`) | - **메인 페이지** (`/`): 학습센터 뷰어 (강의 목록) - **학습센터 뷰어** (`/learning`): 강의 검색/필터 + 카드 목록 (일반 사용자) - **관리자** (`/admin`): 강의 등록(YouTube/PPT/웹 링크 등), 썸네일·AI 추가 등 통합 관리 - **강의 상세**: 카드 클릭 시 유튜브 재생 또는 PPT 뷰어 - **기타 메뉴**: 채팅(`/chat`), AI(`/ai-explore`), **프롬프트 라이브러리**(`/ai-explore/prompts`), **AI 성공 사례**(`/ai-cases`), AX 과제 신청(`/ax-apply`) - **관리자 이벤트 로그**: `http://localhost:8030/admin/thumbnail-events?token={ADMIN_TOKEN}` ### 접속이 안 될 때 (트러블슈팅) 1. **서버가 떠 있는지 확인** 프로젝트 루트에서 `npm start`를 실행하고, 터미널에 `Server started: http://localhost:8030` 같은 로그가 나오는지 확인합니다. 2. **포트 번호 확인** `.env`에 `PORT=...`가 있으면 **그 포트**로 접속합니다. (기본값은 `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`를 사용할 수 있습니다. --- ## 핵심 기능 - 학습센터 UI (좌측 메뉴 + 상단 헤더 + 강의 카드 레이아웃) - **AI 탐색** (`/ai-explore`): 전체 너비 레이아웃, AI 서비스 카드(프롬프트·회의록 등). 검색창에 **「프롬프트」**가 포함된 채 검색(Enter) 시 프롬프트 라이브러리로 이동 - **프롬프트 라이브러리** (`/ai-explore/prompts`): 업무별 기본 프롬프트 카드 선택·미리보기·클립보드 복사 (`data/company-prompts.json`). **좌측 메뉴(채팅·AI·AI 성공 사례·학습센터·AX 과제 신청)는 관리자 여부와 관계없이 접근 가능**(강의 삭제·관리자 대시보드 등 일부 기능은 관리자 모드에서만) - 검색/필터/페이지네이션 - 검색어(`q`) 기반 제목/설명/태그 필터 - 타입(`YouTube`, `PPT`) 필터 - 태그 필터 + 페이지네이션 - 유튜브 강의 등록/시청 - 유튜브 URL 입력 후 목록에 추가 - 강의 상세에서 iframe 임베드로 재생 - PPT 강의 등록/시청 - `.pptx` 파일 업로드 - 상세에서 슬라이드 텍스트 기반 뷰어 제공 - 목록 카드에 PPT 프리뷰(첫 슬라이드 제목 + 장수) 표시 - macOS 환경에서는 `qlmanage` 기반 실제 썸네일(첫 장 이미지) 자동 생성 - 썸네일 백그라운드 큐 - 썸네일 생성은 비동기 큐에서 처리 - 상태값: `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 로그인 중에도 표시). --- ## 프로젝트 구조 ```text ai_platform/ ├─ server.js # Express 서버 진입점 (라우팅, 업로드, 썸네일 큐, DB 연동) ├─ package.json # 의존성 및 npm 스크립트 ├─ .env # 환경 변수 (실제 값, .gitignore 대상) ├─ .env.example # 환경 변수 예시 템플릿 ├─ db/ │ └─ schema.sql # PostgreSQL lectures 테이블 스키마 (서버 기동 시 자동 적용) ├─ scripts/ │ └─ apply-schema.js # 수동 스키마 적용 스크립트 (npm run db:schema) ├─ public/ │ └─ styles.css # 전역 스타일 ├─ views/ │ ├─ partials/ │ │ ├─ nav.ejs # 좌측 공통 네비게이션(하단 관리자·토큰 모달 연동) │ │ └─ admin-token-modal.ejs # 관리자 토큰 입력 모달(`openAdminTokenModal`) │ ├─ learning-viewer.ejs # 학습센터 뷰어 (일반 사용자) │ ├─ learning-admin.ejs # 학습센터 관리 (업로드·삭제·썸네일) │ ├─ chat.ejs # 채팅 │ ├─ ai-explore.ejs # AI 탐색 (전체 너비, 프롬프트 카드·검색) │ ├─ ai-prompts.ejs # 프롬프트 라이브러리 (카드·미리보기·복사) │ ├─ ai-cases.ejs # AI 성공 사례 목록(카드) │ ├─ ai-case-detail.ejs # AI 성공 사례 상세(마크다운) │ ├─ ai-cases-write.ejs # AI 성공 사례 관리자 등록·편집 │ ├─ ax-apply.ejs # AX 과제 신청 │ ├─ lecture-youtube.ejs # 유튜브 강의 상세 (iframe 임베드) │ ├─ lecture-ppt.ejs # PPT 강의 상세 (슬라이드 텍스트 뷰어) │ └─ admin-thumbnail-events.ejs # 썸네일 이벤트 로그 관리자 페이지 ├─ data/ │ ├─ lectures.json # 강의 메타데이터 (ENABLE_POSTGRES=0 또는 DB 폴백 시) │ ├─ company-prompts.json # AI 프롬프트 라이브러리 템플릿 (제목·설명·본문) │ ├─ 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` 사용 | | **썸네일** | 비동기 큐 처리, `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](https://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. **코드 받기** ```bash mkdir -p ~/workspace && cd ~/workspace git clone <저장소 URL> ai_platform cd ai_platform ``` (이미 클론한 경우 예: `cd /Users/dsyoon/workspace/ai_platform`) 3. **의존성·환경** ```bash npm install cp .env.example .env # 편집기로 .env 수정: PORT, ADMIN_TOKEN, DB_*, ENABLE_POSTGRES 등 ``` 4. **DB 스키마** (`ENABLE_POSTGRES=1`일 때) ```bash npm run db:schema ``` 5. **실행 방식 선택** - **포그라운드(테스트)**: `npm start` → 터미널에 `Server started: http://localhost:8030` 확인 후 브라우저 접속 - **백그라운드(PM2)**: 아래 「[PM2로 실행](#pm2로-실행)」 절 참고 (`pm2 start … --name ai_platform`) 6. **접속** 같은 Mac에서: `http://127.0.0.1:8030` (또는 `.env`의 `PORT`). 다른 기기에서 접속하려면 `HOST=0.0.0.0`이 기본이므로, **macOS 방화벽**에서 Node 허용 여부를 확인합니다. --- ### Linux에서 배포 (새 서버) 배포 경로는 예시로 `/var/www/ai_platform`을 둡니다. 배포 사용자·그룹(`www-data` 등)은 배포 정책에 맞게 조정하세요. 1. **시스템 패키지** (Ubuntu/Debian 예시) ```bash sudo apt update sudo apt install -y git build-essential ``` - **Node.js**: 배포판 기본 패키지가 오래된 경우가 많으므로 **[NodeSource](https://github.com/nodesource/distributions)** 또는 **nvm**으로 **v18+** 설치를 권장합니다. - **PostgreSQL 클라이언트/서버**: 원격 DB만 쓰면 클라이언트 라이브러리만으로도 되고, 로컬 DB면 `postgresql` 패키지 설치 후 DB·사용자 생성. - **PPT 변환(선택)**: `sudo apt install -y libreoffice poppler-utils` 2. **코드 배치** ```bash 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. **의존성·환경** ```bash npm install --production cp .env.example .env nano .env # 또는 vim — PORT, ADMIN_TOKEN, DB_*, ENABLE_POSTGRES 등 ``` 4. **DB 스키마** (`ENABLE_POSTGRES=1`일 때) ```bash npm run db:schema ``` 5. **프로세스 관리 (PM2 권장)** ```bash sudo npm install -g pm2 cd /var/www/ai_platform pm2 start server.js --name ai_platform pm2 save pm2 startup # 부팅 시 자동 기동(출력되는 sudo 명령 실행) ``` 6. **방화벽·리버스 프록시** - 외부에 직접 `8030`을 열지 않고 **Nginx/Apache**로 TLS·프록시하는 구성이 일반적입니다. - **Apache2** 예시(모듈·VirtualHost)는 [docs/DEPLOYMENT-xavis.ncue.net.md](docs/DEPLOYMENT-xavis.ncue.net.md)를 참고하세요. - `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회**(또는 스키마 변경 시); 백업·마이그레이션 정책은 운영 환경에 맞게 별도 수립 --- ## 실행 방법 **로컬에서 빠르게 띄우기**는 아래 순서면 됩니다. **새 서버에 맞춰 처음부터 배포**할 때는 위 「서버 배포 (새 머신·처음부터)」의 **macOS** 또는 **Linux** 절을 따르세요. ## 1) 의존성 설치 ```bash npm install ``` ## 2) PostgreSQL 스키마 적용 ```bash npm run db:schema ``` ## 3) 서버 실행 프로젝트 루트에서: ```bash npm start ``` 정상 기동 시 터미널에 `Server started: http://localhost:8030` (또는 설정한 `PORT`)가 출력됩니다. **이 상태에서만** 브라우저로 접속할 수 있습니다. ### PM2로 실행 프로젝트 루트에서 실행해야 `server.js`가 같은 디렉터리의 `.env`를 읽습니다( `dotenv` 기준). **1) PM2 설치 (전역, 한 번)** ```bash npm install -g pm2 ``` **2) 프로젝트 디렉터리로 이동 후 의존성** ```bash cd /Users/dsyoon/workspace/ai_platform npm install cp .env.example .env # 최초 1회 — 값 편집 ``` **3) 앱 기동** ```bash pm2 start server.js --name ai_platform ``` `npm start`와 동일하게 `server.js`를 띄웁니다. 이름만 PM2에서 `ai_platform`으로 관리합니다. **4) 재부팅 후에도 유지 (선택)** ```bash 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로 띄운 경우 재시작 예: ```bash pm2 restart ai_platform ``` 로컬에서 포그라운드로만 확인할 때: ```bash node server.js ``` - 기본 포트: `8030` - 접속 URL: [http://localhost:8030](http://localhost:8030) ### 포트·수신 주소 변경 ```bash PORT=8030 npm start ``` ```bash # 로컬 루프백만 (외부 네트워크 인터페이스에 바인딩하지 않음) HOST=127.0.0.1 npm start ``` 서버는 기본적으로 `HOST=0.0.0.0`으로 바인딩합니다(동일 기기의 `localhost` 접속에 사용). ### 기존 서버 프로세스 종료 터미널을 닫았는데도 이전에 실행한 `node server.js`가 남아 있거나, **포트가 이미 사용 중(`EADDRINUSE`)**이라 새로 `npm start`가 실패할 때, 기존 프로세스를 먼저 종료합니다. **포트로 PID 확인 후 종료 (macOS / Linux, 기본 포트 8030 예시)** ```bash lsof -i :8030 ``` 출력의 `PID` 열 값을 확인한 뒤: ```bash kill PID ``` 응답이 없으면 `kill -9 PID`로 강제 종료할 수 있으나, 다른 Node 작업이 같은 포트를 쓰는지 확인한 뒤 사용하세요. **프로젝트 진입점만 대상으로 종료 (다른 `node` 작업에 영향을 줄 수 있으니 주의)** ```bash pkill -f "node server.js" ``` **PM2로 띄운 경우** ```bash pm2 list pm2 stop ai_platform # 완전히 제거하려면 pm2 delete ai_platform ``` Windows에서는 작업 관리자에서 `Node.js` 프로세스를 종료하거나, PowerShell에서 `Get-NetTCPConnection -LocalPort 8030` 등으로 점유 프로세스를 확인한 뒤 해당 PID를 종료합니다. ### 관리자 토큰/페이지 크기 설정(선택) ```bash ADMIN_TOKEN=my-secret PAGE_SIZE=12 npm start ``` - `ADMIN_TOKEN` 미지정 시 기본값: `xavis-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` ### 채팅 기능 (OpenAI API) - `.env`에 `OPENAI_API_KEY`를 넣은 뒤 **서버를 재시작**하면 채팅 메뉴(`/chat`)에서 OpenAI와 실제 대화가 이루어집니다. - API 키 발급: [OpenAI Platform](https://platform.openai.com/api-keys) - 키 값 앞뒤 공백은 자동으로 제거합니다. - 화면의 모델 선택(`gpt-5.4`, `gpt-5-mini`)은 내부적으로 OpenAI에 전달할 **실제 모델 ID**로 매핑됩니다. 기본값은 각각 `gpt-4o`, `gpt-4o-mini`이며, 필요하면 `.env`에서 `OPENAI_MODEL_DEFAULT`, `OPENAI_MODEL_MINI`로 바꿀 수 있습니다. - **웹 검색(기본 활성):** `OPENAI_WEB_SEARCH`가 `0`이 아니면 OpenAI **Responses API**와 내장 **`web_search`** 도구로 질의에 맞게 웹을 검색한 뒤 답변합니다. 끄려면 `OPENAI_WEB_SEARCH=0`으로 두면 이전과 같이 **Chat Completions**만 사용합니다. 선택적으로 `OPENAI_WEB_SEARCH_COUNTRY`(기본 `KR`), `OPENAI_WEB_SEARCH_CITY`, `OPENAI_WEB_SEARCH_REGION`, `OPENAI_WEB_SEARCH_TIMEZONE`(기본 `Asia/Seoul`)로 검색 맥락을 줄 수 있습니다. - `GET /api/chat/config`는 `{ configured, webSearch }`를 반환합니다(`webSearch`는 위 플래그와 동일). - 스트리밍(`POST /api/chat/stream`)은 SSE로 `data: {JSON}` 줄을 보냅니다. 타입 예: `delta`(본문 조각), `status`(예: `phase: "web_search"` — UI에서 «웹 검색 중…» 표시), `sources`(인용 URL·제목 목록), `done`, `error`. 비스트리밍 `POST /api/chat`은 필요 시 응답 JSON에 `sources` 배열을 포함할 수 있습니다. - 키가 비어 있으면 `POST /api/chat`·`POST /api/chat/stream`은 503을 반환하고, 채팅 화면 상단에 안내 배너가 표시됩니다. ### PPT 썸네일 및 슬라이드 이미지 생성 제어(선택) ```bash 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 패키지) ### `.env` 예시 ```env PORT=8030 HOST=0.0.0.0 ADMIN_TOKEN=xavis-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 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 ``` --- ## 사용 방법 1. 메인 페이지에서 강의 등록 - **유튜브 강의 등록**: 제목 + 유튜브 링크 (+설명) - **PowerPoint 강의 등록**: 제목 + `.pptx` 파일 (+설명) - 두 등록 폼 모두 **태그(쉼표 구분)** 입력 가능 2. 하단 **등록된 강의** 카드에서 항목 클릭 3. 강의 상세 화면에서 시청 - 유튜브: 동영상 재생 - PPT: 슬라이드 텍스트 목록 확인 ### 검색/필터 - 검색어, 타입, 태그를 조합해서 목록을 조회할 수 있습니다. - 결과 목록은 페이지 단위로 분할되어 이동 가능합니다. ### 관리자 삭제 1. 화면의 관리자 모드에서 토큰 입력 후 활성화 2. 강의 카드의 `삭제` 버튼으로 즉시 삭제 3. PPT 강의 삭제 시 업로드 파일도 함께 제거 시도 ### 썸네일 재생성(관리자) - 관리자 모드에서 PPT 카드의 `썸네일 재생성` 버튼으로 수동 재생성 가능 - `실패 썸네일 일괄 재시도` 버튼으로 실패 건을 큐에 일괄 재등록 가능 - `이벤트 로그 페이지`에서 기간/유형/강의ID/사유 필터 조회 가능 - 이벤트 로그는 CSV 다운로드 지원 --- ## 주요 라우트 ### 메뉴별 화면 | 경로 | 설명 | |------|------| | `GET /` | 학습센터 뷰어 (강의 목록) | | `GET /learning` | 학습센터 뷰어 (검색/필터/페이지네이션) | | `GET /admin` | 관리자 대시보드 (강의·썸네일·AI 등) | | `GET /chat` | 채팅 | | `GET /ai-explore` | AI 탐색 (프롬프트·회의록 등 카드, 전체 너비 레이아웃) | | `GET /ai-explore/prompts` | 프롬프트 라이브러리 (업무 시나리오별 기본 프롬프트) | | `GET /ai-cases` | AI 성공 사례 목록(검색·태그 필터, 카드) | | `GET /ai-cases/:slug` | AI 성공 사례 상세 | | `GET /ai-cases/write` | AI 성공 사례 관리(관리자 토큰 필요) | | `POST/PUT/DELETE /api/ai-success-stories` | 사례 CRUD(관리자) · 본문은 `data/ai-success-stories/*.md` | | `GET /ax-apply` | AX 과제 신청 | | `GET /lectures/:id` | 강의 상세 뷰어 (유튜브/PPT) | ### API - `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` 관리자 토큰 기반 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 다운로드 --- ## 최근 업데이트 (요약) - **AI 탐색** (`/ai-explore`): 메인 콘텐츠를 뷰포트 전체 너비로 사용, **프롬프트** 서비스 카드를 첫 번째에 배치, 검색어에 **「프롬프트」**가 포함된 채 검색(Enter) 시 프롬프트 라이브러리로 이동. **좌측 전체 메뉴는 관리자 여부와 관계없이 동일하게 표시·접근 가능** - **프롬프트 라이브러리** (`/ai-explore/prompts`): 시나리오 카드·미리보기·복사 UI, 템플릿 데이터는 `data/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 썸네일은 시스템 도구 상태에 따라 생성 실패할 수 있으며, 이 경우 이미지 없이 텍스트 프리뷰만 표시됩니다. - 썸네일 큐는 단일 프로세스 워커 기준으로 동작합니다(다중 인스턴스 분산 락은 미구현). - 폰트/도형/애니메이션까지 완전 동일 렌더링이 필요하면 별도 문서 렌더러(예: LibreOffice/PDF 변환 파이프라인) 연동이 필요합니다. - 이벤트 로그 페이지의 실시간 그래프는 클라이언트 폴링 기반이며, 다수 접속 시 폴링 주기 조정이 필요할 수 있습니다.