# ITN-HUB

> **2026 신유형 공공저작물 개방사업 · 3단계 법률검토 관리 시스템**
>
> 신청기관 명부·담당자 관리부터 Mattermost 채널 자동 생성, 권리확인·권리처리 판정,
> 사업 현황 대시보드까지 한 곳에서 처리한다.

`Spring Boot 3.3.5`　`Java 21`　`React 18`　`PostgreSQL 16`　`MyBatis`　`Flyway`　`Mattermost`

---

## 목차

- [무엇을 하는 시스템인가](#무엇을-하는-시스템인가)
- [화면 구성](#화면-구성)
- [시작하기](#시작하기)
- [환경변수](#환경변수)
- [업무 흐름](#업무-흐름)
- [핵심 규칙](#핵심-규칙)
- [프로젝트 구조](#프로젝트-구조)
- [API](#api)
- [테스트](#테스트)
- [배포](#배포)

---

## 무엇을 하는 시스템인가

기존 Excel VBA 매크로(`M06_Mattermost_기관목록`, `M07_Mattermost_채널일괄생성`)로 하던 작업을
웹으로 옮긴 것이다. 47개 신청기관을 대상으로 다음 흐름을 처리한다.

```
 신청목록 엑셀        담당자            채널 2종         권리확인 / 권리처리       보고서
    업로드      →     배정      →     자동 생성    →      변호사 판정       →     제출
   기관 명부      신청기관·문정원     문정원·법률검토      엑셀 업로드 후          사업관리
                 아이티앤·변호사     + 안내글 핀 고정        건별 판정
```

> **⚠ 주의**
>
> 이 애플리케이션은 **실제 운영 Mattermost 서버(`https://hub.iten.co.kr`)에 실제 채널을 만든다.**
> 처음 쓸 때는 실제 기관이 아니라 테스트 기관(예: 연번 `999`)으로 한 바퀴 돌려보고 시작할 것.
>
> `MATTERMOST_TOKEN`은 **시스템 관리자(system-admin) 권한** 계정의 Personal Access Token이어야 한다.
> 팀 전체 범위에서 비공개 채널을 조회해야(레거시 채널 복구) 중복 생성을 막을 수 있기 때문이다.

---

## 화면 구성

| 메뉴 | 하는 일 |
| :-- | :-- |
| **Dashboard** | KPI 7타일 · 단계별 칸반(카드 드래그로 단계 이동) · 최근 진행 기관 |
| **채널관리** | 신청목록 시트 업로드 · 담당자 4역할 배정 · Mattermost 채널 2종 생성 |
| **기관관리** | 기관 상세. 업무단계 스트립 + 7개 탭(채팅·자료·권리확인·권리처리·RE·타임라인·업무메모) |
| **담당자관리** | 담당자 등록·수정·삭제, 회원명단 엑셀 일괄 업로드 |
| **사업관리** | 3단계 대상 기관 현황 · 신청 확인 연락 이력 · 기관별 최종 보고서 |
| **시스템관리** | 채널 초기화(미리보기 → 확인문구 → 실행) |

화면 곳곳의 버튼 옆 **`?` 아이콘**을 누르면 실제 화면 캡처가 담긴 설명서가 열린다(19개 항목).

---

## 시작하기

### 요구사항

| 항목 | 버전 / 비고 |
| :-- | :-- |
| JDK | **21** |
| PostgreSQL | **16** |
| Node.js | 불필요 — `mvn package`가 v20.17.0을 내려받아 프런트를 함께 빌드한다 |
| Docker | 백엔드 테스트에만 필요(Testcontainers) |

### 빌드

```bash
mvn -DskipTests package
```

`frontend-maven-plugin`이 React 앱을 빌드해 `src/main/resources/static/`에 넣고,
그 결과가 **단일 jar 하나**로 패키징된다. 프런트를 따로 빌드할 필요가 없다.

### 실행

```bash
export DB_URL=jdbc:postgresql://localhost:5432/itnhub
export DB_USERNAME=itnhub
export DB_PASSWORD=...
export MATTERMOST_URL=https://hub.iten.co.kr
export MATTERMOST_TOKEN=...
export MATTERMOST_TEAM_ID=...
export APP_ADMIN_USERNAME=admin
export APP_ADMIN_PASSWORD=...

java -jar target/itnhub-0.1.0.jar
```

기동하면 Flyway가 `itnhub` 스키마와 테이블(V1~V12)을 **자동으로 만든다.** 수동 DDL은 없다.

### 프런트만 반복 개발할 때

```bash
cd frontend && npm install && npm run dev   # Vite가 /api 를 :8877 로 프록시
mvn spring-boot:run                          # 백엔드는 따로 띄워 둔다
```

---

## 환경변수

`.env.example`을 복사해 값을 채운다. **실제 값이 든 파일은 커밋하지 않는다.**

### 필수 — 하나라도 비면 기동하지 않는다

| 변수 | 설명 | 예시 |
| :-- | :-- | :-- |
| `DB_URL` | PostgreSQL JDBC URL | `jdbc:postgresql://192.168.0.60:5432/itnhub` |
| `DB_USERNAME` | DB 계정 | `itnhub` |
| `DB_PASSWORD` | DB 비밀번호 | |
| `MATTERMOST_URL` | Mattermost 베이스 URL | `https://hub.iten.co.kr` |
| `MATTERMOST_TOKEN` | **system-admin 권한** Personal Access Token | |
| `MATTERMOST_TEAM_ID` | 채널을 만들 팀 ID | |
| `APP_ADMIN_USERNAME` | 앱 로그인 계정 | `admin` |
| `APP_ADMIN_PASSWORD` | 앱 로그인 비밀번호(충분히 긴 무작위 값) | |

### 선택 — 기본값 있음

| 변수 | 기본값 | 설명 |
| :-- | :-- | :-- |
| `MATTERMOST_TEAM_NAME` | `itn-hub` | 채널 URL을 만들 때 쓰는 팀 이름 |
| `MATTERMOST_DEFAULT_USER_PASSWORD` | `test1234!` | 자동 생성되는 담당자 계정의 초기 비밀번호 |

> **참고 —** `application.yml`의 `spring.datasource.hikari.schema: itnhub` 는 **지우면 안 된다.**
> `spring.flyway.schemas`는 Flyway 전용 커넥션에만 적용돼서, 이 설정이 없으면 런타임 커넥션이
> `public` 스키마를 보고 `relation "organization" does not exist` 로 죽는다.

---

## 업무 흐름

### 진행단계 12단계

```
 ① 신청  →  ② 목록접수  →  ③ 예비검토  →  ④ 변호사 배당
       →  ⑤ 법률검토(권리확인)  →  ⑥ RE:확인  →  ⑦ 법률검토(권리확인) 완료
       →  ⑧ 법률검토(권리처리)  →  ⑨ RE:처리  →  ⑩ 법률검토(권리처리) 완료
       →  ⑪ 법률검토 최종완료   →  ⑫ 보고서 작성
```

- 채널을 만들면 **①신청이 자동으로 시작**된다. 그 뒤로는 **전부 수동**이다 — 자료를 올리거나
  게시글을 써도 단계가 저절로 넘어가지 않는다(요청자 확정 사항).
- 단계 변경 이력은 전부 **타임라인 탭**에 남는다.

### 최초 사용 순서

1. PostgreSQL을 준비하고 환경변수를 채운다 → 기동하면 스키마가 만들어진다
2. 브라우저로 접속해 관리자 계정으로 로그인
3. **담당자관리** 에서 담당자를 등록한다(회원명단 엑셀 일괄 업로드 가능)
4. **채널관리** 에서 신청목록 시트를 올려 기관 명부를 만들고, 기관마다 담당자 4역할을 배정한다
5. **채널 생성** — 채널 2종 + 담당자 계정 + 안내글이 한 번에 준비된다
6. **기관관리 › 권리확인/권리처리** 탭에서 조사 엑셀을 올리고 건별로 판정한다
7. **사업관리** 에서 연락 이력을 남기고 최종 보고서를 올린다

---

## 핵심 규칙

수정할 때 깨뜨리면 안 되는 약속들이다.

| 규칙 | 내용 |
| :-- | :-- |
| **채널은 항상 2개** | 기관마다 `NNN_기관명 (문정원)` · `NNN_기관명 (법률검토)` 가 쌍으로 만들어진다. 게시글·자료도 늘 양쪽에 함께 반영된다. 한쪽만 처리하는 코드는 버그다. |
| **재실행 안전** | 채널 생성·시드 업로드·채널 초기화 모두 여러 번 실행해도 안전하다. 이미 처리된 것은 건너뛰고 없는 것만 만든다. 새 로직에서도 이 원칙을 지킨다. |
| **채널 ⇔ 단계** | 채널이 있으면 진행단계가 반드시 있고, 단계가 없으면 채널도 없다(V11이 이 불변식을 맞춘다). 대시보드 칸반의 **단계 미지정** 컬럼은 **채널 미생성 기관만** 담는다. |
| **엑셀 재업로드** | 같은 파일을 다시 올려도 **웹에서 고친 값(`web_edited_at`)은 보존**되고 조사원 영역만 덮어쓴다. 병합 기준은 순번이 아니라 **파일의 행 순서**다(원본에 순번 중복이 있다). |
| **제3자 권리처리** | 권리 일부 보유·권리 미보유는 `권리처리 추진` / `권리처리 추진 미희망` 택일, 초상권 포함은 `개방불가`까지 3택. 추진이면 공공누리 유형이 `보류`로, 미희망이면 기존 유형이 자동 선택되고 비고에 사유가 적힌다. |
| **작성자 자동 기록** | 업무메모·연락 이력의 작성자는 화면에서 고르지 않는다. 로그인 사용자를 서버가 기록하고, 수정해도 최초 작성자·작성시각은 유지된다. |
| **연번은 문자열** | 기관 코드는 `연번1_연번2`(`001_00`) 형식이다. 숫자로 바꾸면 앞자리 0이 사라져 매칭이 전부 깨진다. |

---

## 프로젝트 구조

```
itnhub/
├─ src/main/java/kr/itn/itnhub/
│  ├─ org/          기관 명부·배정              ├─ review/     권리확인
│  ├─ contact/      담당자                      ├─ process/    권리처리
│  ├─ provision/    채널 생성(멱등)             ├─ stage/      진행단계·이력
│  ├─ mattermost/   Mattermost API 클라이언트   ├─ timeline/   타임라인 집계
│  ├─ feed/         채널 게시글·파일            ├─ memo/       업무메모
│  ├─ dashboard/    대시보드 집계               ├─ project/    사업관리
│  ├─ seed/         신청목록 시트 시드          └─ system/     채널 초기화
│  └─ config/       보안·설정
│
├─ src/main/resources/
│  ├─ db/migration/     Flyway V1~V12
│  ├─ mapper/           MyBatis XML
│  └─ application.yml
│
├─ frontend/src/
│  ├─ components/       화면 19개 (Dashboard, OrgOverview, ReviewBoard …)
│  ├─ help/             버튼 옆 「?」 도움말 — 본문과 캡처가 여기 한 곳에 있다
│  ├─ api/client.ts     모든 서버 호출이 지나는 단일 창구
│  └─ stages.ts         12단계 라벨 정본
│
├─ deploy/               176 서버 배포 정의 (compose · env 템플릿 · 절차서)
└─ Dockerfile            프런트까지 함께 빌드하는 멀티스테이지
```

---

## API

| 영역 | 엔드포인트 |
| :-- | :-- |
| 기관 | `GET /api/orgs` · `PUT /api/orgs/{id}/assignments` · `POST /api/orgs/{id}/channels` |
| 진행단계 | `PUT /api/orgs/{id}/stage` · `GET /api/orgs/{id}/stage-history` |
| 채팅·자료 | `/api/orgs/{id}/posts` `messages` `notice` `files` · `GET /api/files/{fileId}` |
| 권리확인 | `/api/orgs/{id}/review` `import` `download` · `PUT …/review/bulk` |
| 권리처리 | `/api/orgs/{id}/process` `import` `download` · `PUT …/process/bulk` |
| 대시보드 | `GET /api/dashboard` — KPI·칸반·최근 진행 기관을 한 번에 |
| 사업관리 | `GET /api/projects` · `/api/orgs/{id}/contact-logs` · `/api/orgs/{id}/reports` |
| 업무메모 | `GET` `POST` `PUT` `DELETE /api/orgs/{id}/memos` |
| 타임라인 | `GET /api/orgs/{id}/timeline` |
| 시스템 | `GET /api/system/reset/preview` · `POST /api/system/reset/channels` |

전 구간 로그인이 필요하고(`/api/auth/login` 제외), 변경 요청은 CSRF 토큰을 함께 보낸다.

---

## 테스트

```bash
mvn test                    # 백엔드 221건 — Testcontainers로 PostgreSQL을 띄우므로 Docker 필요
cd frontend && npm test     # 프런트 191건
```

---

## 배포

Docker 기준 상세 절차는 **[`deploy/README.md`](deploy/README.md)** 에 있다.

```bash
docker build -t itnhub:latest .
sudo cp deploy/itnhub.env.example /etc/itnhub/itnhub.env   # 값 채우기
cd /opt/itnhub && sudo docker compose up -d
```

| | |
| :-- | :-- |
| 산출물 | **단일 jar** — React가 그 안에 있어 서버에 Node도 웹서버도 필요 없다 |
| 영속 볼륨 | **불필요** — 업로드 파일은 DB(`org_report`)와 Mattermost에 저장된다 |
| DB | 컨테이너로 띄우지 않고 기존 PostgreSQL을 그대로 쓴다 |
| 시간대 | 컨테이너를 `Asia/Seoul`로 고정 — UTC면 도달 일시·D+n이 9시간 어긋난다 |
| 헬스체크 | `GET /login` 200 (actuator 미포함) |

> **⚠ 빌드 머신에는 인터넷이 필요하다**(Node·npm·Maven을 내려받는다). 실행 서버는 필요 없다.
> 사내망이 막혀 있으면 Maven 미러와 `nodeDownloadRoot`를 사내 저장소로 돌려야 한다.
