Initial deployment setup
Deploy staging / deploy (push) Failing after 6s

This commit is contained in:
root
2026-08-31 16:45:24 +09:00
commit 33453ecc55
3475 changed files with 850363 additions and 0 deletions
+129
View File
@@ -0,0 +1,129 @@
# Baron SSO Server-Side App Demo (Express.js)
이 프로젝트는 `baron-sso``server-side-app` RP를 테스트하기 위한 단순한 Express.js 데모입니다.
## 목적
이 데모는 다음을 확인하기 위한 용도입니다.
1. confidential client 기반 OIDC Authorization Code 로그인
2. RP 로컬 세션 생성 및 유지
3. `Back-Channel Logout URI` 호출 수신
4. `logout_token` 검증 후 로컬 세션 즉시 파기
5. `BARON_SESSION_VALIDATION_ENABLED=false`일 때 access token 만료 후 refresh token 갱신으로 세션 종료 확인
## 이 프로젝트 적용 원칙
- BARON-SSO는 인증과 현재 테넌트 문맥 확인까지만 사용합니다.
- 최종 권한 부여, 관리자 여부 판정, 프로젝트 접근 제어는 모두 내부 DB에서 처리합니다.
- 따라서 OIDC 로그인 완료 후 RP 세션에는 최소한의 사용자 식별자와 `tenant_id` 만 저장하고, 이후 내부 사용자 매핑과 권한 조회를 별도 계층에서 수행하는 구성이 적합합니다.
- 초기 운영 역할은 `PROJECT_MANAGER` 중심으로 두고, 채널 관리자 역할은 추후 필요 시 확장합니다.
- 관리자 권한 설정 페이지는 관리자 콘솔 메뉴에 추가하는 방향을 기준으로 합니다.
- 사용자 피드백에는 비밀글 기능을 추가하고, 조회 제한은 내부 DB 권한 정책으로 제어합니다.
## 이 저장소 기준 BARON-SSO 연결값
관리자 콘솔의 테넌트 OAuth 설정에는 아래 값을 기준으로 입력합니다.
```text
Login Type: OAuth 2.0
Login Button Type: CUSTOM
Login Button Name: BARON SSO 로그인
Client ID: 838cd69d-e722-41da-9f79-b3c42a509ef2
Client Secret: <BARON 관리자 콘솔에서 발급된 값>
Authorization Code Request URL: https://sso.hmac.kr/oidc/oauth2/auth
Access Token Request URL: https://sso.hmac.kr/oidc/oauth2/token
User Profile Request URL: https://sso.hmac.kr/oidc/userinfo
Scope: openid profile email
Email Key in Response of User Profile: email
Subject Key in Response of User Profile: sub
Name Key in Response of User Profile: name
Department Key in Response of User Profile: department
Redirect URI: http://localhost:3003/api/auth/baron-sso/callback
```
설정 메모:
- 내부 권한 매핑 기준 키는 전화번호가 아니라 `sub`입니다.
- `name`, `department`는 BARON userinfo 응답에 실제로 존재할 때만 화면 표시용으로 사용합니다.
- 테넌트 식별, 관리자 여부, 프로젝트 권한은 BARON에서 결정하지 않고 내부 DB에서 결정합니다.
- `Client Secret`은 저장소에 하드코딩하지 말고 관리자 콘솔에서 직접 입력합니다.
## 사전 준비
1. `baron-sso` 프로젝트가 실행 중이어야 합니다.
2. `baron_net` 네트워크가 생성되어 있어야 합니다.
3. devfront에서 `server-side-app` 타입 RP를 생성해야 합니다.
## 권장 RP 설정
예시:
```text
Type: server-side-app
Client ID: <생성된 client id>
Client Secret: <생성된 secret>
Redirect URI: http://localhost:4444/callback
Back-Channel Logout URI: http://172.16.x.x:4444/backchannel-logout
SID Claim Required: off
```
주의:
- `Back-Channel Logout URI`는 브라우저 기준이 아니라 Baron backend가 실제로 접근 가능한 주소여야 합니다.
- Docker 환경에서 `localhost`는 backend 컨테이너 자신을 가리킬 수 있으므로, 필요하면 사설 IP 또는 Docker 서비스명을 사용해야 합니다.
## 실행
```bash
docker-compose up --build
```
## 환경 변수
- `PORT`: 기본값 `4444`
- `SESSION_SECRET`: Express session secret
- `OIDC_ISSUER_URL`: Baron OIDC issuer URL
- `OIDC_CLIENT_ID`: server-side-app client id
- `OIDC_CLIENT_SECRET`: server-side-app client secret
- `OIDC_REDIRECT_URI`: callback URL
- `OIDC_CLIENT_AUTH_METHOD`: 기본값 `client_secret_basic`, 필요 시 `client_secret_post`
- `BARON_API_BASE_URL`: Baron backend/public gateway URL
- `BARON_BACKCHANNEL_JWKS_URL`: Baron Back-Channel Logout JWKS URL
- `BARON_SESSION_VALIDATION_ENABLED`: `false`로 두면 Baron 세션 재검증을 끄고, access token 만료 후 refresh token 갱신으로 세션 종료를 확인합니다. 기본값은 `true`입니다.
## 라우트
```text
GET /
GET /login
GET /callback
GET /profile
GET /logout
POST /backchannel-logout
```
## 동작 방식
1. `/login`에서 state/nonce를 만들고 Baron authorize endpoint로 이동
2. `/callback`에서 authorization code를 token으로 교환
3. ID Token의 `sid/sub`를 현재 RP 세션 ID와 매핑
4. `BARON_SESSION_VALIDATION_ENABLED=true`이면 요청마다 Baron `GET /api/v1/user/me`를 호출해 세션을 재검증
5. `BARON_SESSION_VALIDATION_ENABLED=false`이면 access token 만료 후 Hydra token endpoint로 refresh token 갱신을 시도
6. `invalid_grant`가 오면 로컬 세션을 파기
7. Baron이 `/backchannel-logout`으로 `logout_token` 전송 시에도 세션을 즉시 파기
## 테스트 포인트
정상 동작 시 아래 로그 흐름이 보여야 합니다.
```text
[로그인 시작]
[콜백] Authorization Code -> Token 교환 성공
[세션 매핑] 등록 완료
[백채널 로그아웃] 요청 수신
[백채널 로그아웃] 토큰 검증 성공
[백채널 로그아웃] 세션 파기 완료
[백채널 로그아웃] 처리 완료
[프로필] 비로그인 상태로 접근하여 루트로 이동
```
+119
View File
@@ -0,0 +1,119 @@
# Back-Channel Logout 처리 시퀀스
이 문서는 `baron-sso-server-side-demo`가 Baron SSO로부터 `POST /backchannel-logout` 요청을 받았을 때 어떤 순서로 동작하는지 정리합니다.
## 개요
이 데모 앱은 Baron SSO가 전송한 `logout_token`을 수신하면, 다음 순서로 처리합니다.
1. 요청 본문에서 `logout_token`을 읽습니다.
2. Baron이 서명한 토큰인지 JWKS로 검증합니다.
3. `sid` 또는 `sub`를 기준으로 로컬 세션을 찾습니다.
4. `express-session` 저장소에서 해당 세션을 삭제합니다.
5. 세션 매핑을 제거하고 `200` 응답을 반환합니다.
## 시퀀스 다이어그램
```mermaid
sequenceDiagram
autonumber
participant Baron as Baron SSO
participant RP as baron-sso-server-side-demo
participant JWKS as Baron Back-Channel JWKS
participant Store as express-session Store
Baron->>RP: POST /backchannel-logout\nlogout_token=<jwt>
RP->>RP: logout_token 추출
RP->>JWKS: JWKS 조회 후 서명 검증
JWKS-->>RP: public key
RP->>RP: iss / aud / events / nonce / jti 검증
RP->>RP: sid 또는 sub로 세션 매핑 조회
RP->>Store: sessionStore.destroy(sessionId)
Store-->>RP: 삭제 완료
RP->>RP: 세션 매핑 제거
RP-->>Baron: 200 OK\n{ success: true }
```
## 실제 구현 위치
- 요청 수신 및 처리 엔드포인트: [`backchannel-logout.js`](../backchannel-logout.js)
- 라우트 등록과 세션 매핑: [`app.js`](../app.js)
- 동작 설명: [`README.md`](../README.md)
## 동작 상세
### 1. 요청 수신
`app.js`에서 아래 라우트를 등록합니다.
```javascript
app.post(
'/backchannel-logout',
backchannelLogoutManager.handleBackchannelLogout,
);
```
이 요청은 `application/x-www-form-urlencoded` 형식으로 전달되며, 본문에는 `logout_token`이 포함됩니다.
### 2. 토큰 추출
`backchannel-logout.js``req.body.logout_token`을 읽어서 빈 값인지 확인합니다.
- 값이 없으면 `400 Bad Request`
- 값이 있으면 다음 단계로 진행
### 3. JWT 검증
데모 앱은 Baron의 백채널 JWKS를 사용해 `logout_token`을 검증합니다.
검증 항목은 다음과 같습니다.
- 서명 검증
- `iss` 일치
- `aud`에 현재 RP `clientId` 포함
- `nonce` 미포함
- `events`에 back-channel logout 이벤트 포함
- `sid` 또는 `sub` 존재
- `jti` 존재 및 재사용 방지
### 4. 세션 탐색
로그인 성공 시 저장한 `sid` / `sub` 매핑을 이용해 대상 세션을 찾습니다.
우선순위는 다음과 같습니다.
1. `sid`로 탐색
2. `sid` 매칭이 없으면 `sub`로 fallback
### 5. 세션 파기
매칭된 세션 ID가 있으면 `express-session` 저장소에서 직접 삭제합니다.
```javascript
sessionStore.destroy(sessionId, callback);
```
삭제 후에는 세션 ID와 `sid` / `sub` 매핑도 함께 제거합니다.
### 6. 응답 반환
정상 처리되면 `200 OK`와 함께 아래 응답을 반환합니다.
```json
{
"success": true,
"destroyedSessionCount": 1
}
```
## 운영 관점 메모
- 이 데모는 백채널 로그아웃 외에도, 각 요청마다 Baron 세션을 재검증하는 경로를 별도로 가집니다.
- `BARON_SESSION_VALIDATION_ENABLED=false`로 두면 재검증을 끄고 백채널 로그아웃만 확인할 수 있습니다.
- Baron이 데모 앱에 직접 접근할 수 있어야 백채널 로그아웃이 성공합니다.
## 관련 파일
- [`backchannel-logout.js`](../backchannel-logout.js)
- [`app.js`](../app.js)
- [`README.md`](../README.md)
+350
View File
@@ -0,0 +1,350 @@
# API 패키징 작업 계획
- 작성일: 2026-08-25
- 목적: 현재 ABC User Feedback와 커스터마이징 기능을 다른 서비스에서 사용할 수 있는 공식 API 패키지로 정리한다.
- 현재 상태: 기능별 API는 대부분 존재하고 Swagger도 연결되어 있으나, 외부 배포용 API 계약과 스키마는 아직 정리되지 않았다.
## 1. 현재 구조
### 1.1 NestJS API
| 구분 | 현재 주소 | 인증 | Swagger |
|---|---|---|---|
| 공개 연동 API | /api/... | x-api-key | /docs |
| 관리자 API | /api/admin/... | JWT + 권한 | /admin-docs |
| Swagger JSON | /docs-json, /admin-docs-json | 서버 설정에 따름 | 생성 가능 |
현재 로컬 확인 주소:
- 공개 API 문서: http://127.0.0.1:4000/docs
- 관리자 API 문서: http://127.0.0.1:4000/admin-docs
- 공개 OpenAPI JSON: http://127.0.0.1:4000/docs-json
- 관리자 OpenAPI JSON: http://127.0.0.1:4000/admin-docs-json
Swagger 설정은 apps/api/src/main.ts에 있으며, 공개 문서 생성 스크립트는 apps/api/src/scripts/build-swagger-docs.ts에 있다.
### 1.2 Secretary API
Secretary API는 FastAPI 자동 문서를 사용한다.
- Swagger UI: :8010/docs
- OpenAPI JSON: :8010/openapi.json
현재 Secretary API에는 SSO 기반 접근 정보, workspace, ticket, 댓글, 내부 메모, 첨부파일, 담당자 관련 API가 포함되어 있다.
### 1.3 Next.js API Route
apps/web/src/pages/api/support/* 아래의 API는 화면과 Secretary API 사이를 연결하는 내부 프록시다.
이 경로들은 NestJS Swagger 문서에는 포함되지 않는다. 외부 패키지에서 직접 사용할 공식 API로 제공하려면 NestJS 또는 Secretary API의 공개 계약으로 승격해야 한다.
## 2. 현재 구현된 API 범위
### 2.1 공개 피드백 API
- 프로젝트·채널 조회
- 채널 필드 조회
- 피드백 생성
- 피드백 목록 조회
- 피드백 검색
- 피드백 상세 조회
- 피드백 수정·삭제
- 피드백과 이슈 연결·해제
- 댓글 CRUD
- 내부 댓글 구분
- 댓글 첨부파일 업로드·조회·삭제
- 피드백 첨부 이미지 업로드
- 카테고리 조회·관리
- 이슈 생성·조회·검색·수정·삭제
관련 컨트롤러:
- apps/api/src/domains/api/feedback.controller.ts
- apps/api/src/domains/api/v2/feedback.controller.ts
- apps/api/src/domains/api/issue.controller.ts
- apps/api/src/domains/api/channel.controller.ts
- apps/api/src/domains/api/project.controller.ts
### 2.2 관리자 API
- 프로젝트·채널·필드 관리
- 피드백 관리자 검색·수정·삭제
- 피드백 상태 처리
- 피드백 담당자 지정
- 이슈 관리자 지정
- 이슈 상태 처리
- Gitea 이슈 생성·연결·동기화
- 댓글·내부 메모 관리
- 통계 조회
- 다중 프로젝트 통합 대시보드
- 관리자 권한·역할 관리
통합 대시보드 API:
GET /api/admin/dashboard/overview
현재 대시보드 API는 사용자의 프로젝트 권한을 기준으로 프로젝트별 피드백·이슈 요약과 Todo 항목을 집계한다.
### 2.3 Secretary API
- SSO 사용자 접근 정보
- workspace 목록 및 폼 템플릿
- ticket 생성·조회·상세 조회
- 댓글 CRUD
- 내부 메모 CRUD
- 담당자 후보 조회 및 지정
- 이슈 연결
- 승인 처리
- 첨부파일 저장·조회
- R2 기반 파일 저장
## 3. 현재 Swagger 문서화 상태
### 완료된 부분
- [x] NestJS Swagger 모듈 연결
- [x] 공개 API 문서와 관리자 API 문서 분리
- [x] 공개 API Key 인증 정의
- [x] 관리자 JWT 인증 정의
- [x] 주요 피드백·이슈 API의 ApiTags, ApiParam, ApiOperation 일부 적용
- [x] 일부 요청·응답 DTO에 ApiProperty 적용
- [x] FastAPI 자동 OpenAPI 문서 제공
- [x] 공개 Swagger JSON 생성 스크립트 존재
### 보완이 필요한 부분
- [x] 통합 대시보드 응답 DTO 정의
- [x] 통합 대시보드 날짜·프로젝트 필터 파라미터 문서화
- [x] 관리자 피드백 컨트롤러에 ApiTags와 상세 operation 문서 추가
- [x] 댓글 CRUD 요청·응답 DTO 정의
- [x] 내부 메모와 일반 댓글의 공개 범위 문서화
- [x] 첨부파일 multipart 요청 스키마 정의
- [x] 담당자·상태·우선순위 enum 문서화
- [x] 오류 응답 형식 표준화
- [x] 동적 필드의 요청·응답 계약 정의
- [x] 내부 workspace mapping API의 Swagger 공개 범위 재검토
- [x] Next.js 내부 프록시 API와 공식 API의 역할 분리
### 1차 보완 완료 기록
- `DashboardOverviewResponseDto`와 하위 통계·Todo·상태 스키마를 추가했다.
- `GET /api/admin/dashboard/overview`의 날짜 범위와 쉼표 구분 프로젝트 필터를 Swagger에 명시했다.
- 관리자 피드백 API에 `admin-feedbacks` 태그, 경로 파라미터, operation 설명, 동적 body 예시, 생성·수정·삭제 응답 설명을 추가했다.
- 공개 OpenAPI JSON과 관리자 OpenAPI JSON을 함께 생성하도록 `apps/api/src/scripts/build-swagger-docs.ts`를 보완했다.
- 공통 오류 응답, enum 문서화, 동적 필드 계약은 다음 보완 작업으로 남겨두었다.
### 2차 보완 완료 기록
- Secretary 댓글 요청·응답 모델에 작성자, 공개 범위, 첨부파일 메타데이터, 수정·삭제 가능 여부의 설명을 추가했다.
- `GET/POST/PUT/DELETE /api/tickets/{ticketId}/comments...`를 공개 댓글 계약으로 명시하고, `GET/POST/PUT/DELETE /api/tickets/{ticketId}/internal-memos...`를 관리자 전용 계약으로 분리했다.
- 공개 댓글 API는 관리자 권한으로 호출하더라도 `is_internal=false`로 저장되며, 내부 메모는 전용 API에서만 생성되도록 경계를 고정했다.
- 지원 요청 생성 API에 JSON과 multipart/form-data 계약을 모두 문서화하고, 반복 가능한 `attachments` binary 필드와 JSON 문자열 `extra_fields`를 명시했다.
- 첨부파일 댓글 API도 `content`와 반복 가능한 `attachments` 입력을 OpenAPI에 노출하도록 명시적인 multipart 파라미터로 변경했다.
- 삭제 응답을 `TicketDeleteResponse` DTO로 고정해 `deleted`, `ticket_id`, `comment_id`, `feedback_id`를 문서화했다.
- Secretary API는 FastAPI 자동 OpenAPI 엔드포인트(`/openapi.json`)에서 위 계약을 산출하며, 실행 환경에 의존성 설치 후 `/docs`에서 확인한다.
### 3차 보완 완료 기록
- 피드백 상태 enum을 `INIT`, `ON_REVIEW`, `DETAILED_REVIEW`, `IN_PROGRESS`, `RESOLVED`, `PENDING`으로 고정하고, 이슈 상태와 동일한 6단계 값임을 Swagger에 명시했다.
- 피드백 우선순위 enum을 `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`로 문서화했다.
- 관리자 피드백 수정 API에 상태, 우선순위, 담당자 ID·tenant ID·이름·이메일 필드의 요청 예시와 enum을 추가했다.
- 통합 대시보드 Todo 응답에 담당자 정보를 포함하고, 피드백 상태·우선순위 타입을 enum으로 제한했다.
- NestJS와 Secretary API의 오류 응답을 `code`, `message`, `error`, `statusCode`, `path` 공통 구조로 표준화했다. 추가 오류 정보는 `details`에 담는다.
- NestJS 관리자 피드백·통합 대시보드 API와 Secretary API OpenAPI에 공통 오류 응답 스키마를 노출했다.
### 4차 보완 완료 기록
- 채널 필드 조회 API(`/projects/{projectId}/channels/{channelId}/fields`)를 동적 피드백 계약의 기준점으로 명시했다. 클라이언트는 이 응답으로 필드 키, format, property, status, select 옵션을 먼저 확인한다.
- 공개·관리자 피드백 생성 API에 동적 JSON 요청 스키마를 연결했다. `title`, `contents`, `Category`, `IP`, `MAC_address`는 대표 예시이며 실제 입력 키는 채널 설정을 따른다.
- `issueNames`는 피드백 필드로 저장되지 않는 이슈 연결용 제어 필드임을 문서화했다.
- 이미지 첨부 생성 API에 multipart 스키마를 연결하고, 동적 비파일 필드와 반복 가능한 `images` binary 파일 입력을 구분했다.
- 피드백 검색 응답은 실제 구현처럼 동적 필드가 최상위에 펼쳐지는 `items`와 페이지네이션 `meta` 구조로 문서화했다.
- 동적 검색 `query` 값에 문자열·숫자·배열·`gte/lt` 범위 조건을 문서화했다.
- 관리자 피드백 수정 API는 공통 관리 필드(상태·우선순위·담당자)를 명시하면서, 나머지 허용 필드는 채널 필드 설정에서 발견하도록 설명했다.
### 5차 보완 완료 기록
- Secretary API가 ABC API의 `GET /api/internal/support/workspace-mappings`를 서비스 간 동기화에만 사용하도록 확인했다.
- workspace mapping endpoint는 `MASTER_API_KEY` 서비스 인증을 사용하므로 공개·관리자 Swagger에서 제외했다. 이 키는 외부 패키지 소비자에게 제공하는 API Key가 아니다.
- Next.js의 `/api/support/*` 경로는 브라우저 세션과 Secretary API 사이의 내부 BFF(proxy)로 분류하고, 외부 패키지용 공식 API는 Secretary API와 NestJS 공개·관리자 API로 한정했다.
- Next.js proxy 경로는 Swagger 산출 대상이 아니며, 외부 연동 문서와 SDK 계약에 포함하지 않는다는 경계를 명시했다.
## 4. 외부 패키지용 API 계약 설계
### 4.1 API 영역 분리
외부 패키지에서는 다음 세 영역을 분리한다.
1. 사용자용 피드백 API
2. 관리자용 운영 API
3. 내부 Secretary 업무 API
외부 사용자에게 관리자 API나 내부 API 권한이 노출되지 않도록 인증 체계와 문서도 분리한다.
### 4.2 권장 버전 정책
/api/v1/projects/{projectId}/channels/{channelId}/feedbacks
/api/v1/projects/{projectId}/issues
/api/v1/admin/...
기존 경로는 호환성을 위해 유지하고, 패키지용 공식 계약은 v1 버전으로 고정하는 것을 권장한다.
### 4.3 공통 응답 형식
성공 응답과 오류 응답을 모든 API에서 일관되게 정의한다.
{
"data": {},
"meta": {
"requestId": "..."
}
}
오류 예시:
{
"code": "NOT_FOUND",
"message": "Feedback was not found.",
"error": "NOT_FOUND",
"statusCode": 404,
"path": "/api/feedbacks/34",
"details": {}
}
### 4.4 페이지네이션·검색·정렬
- page, limit 또는 cursor 방식 중 하나로 표준화
- 최대 limit 제한
- 정렬 가능 필드 화이트리스트 적용
- 제목·내용·작성자 검색의 검색 범위 명시
- 비밀글 접근 시 작성자·관리자 권한 검증
- 응답에 total, page, limit, hasNext 포함
### 4.5 동적 필드
현재 피드백 데이터는 채널 필드 설정에 따라 동적으로 구성된다.
현재 API 계약:
{
"title": "...",
"contents": "...",
"Category": "ERROR_QNA",
"IP": "...",
"MAC_address": "...",
"issueNames": ["Login error"]
}
- 실제 필드 키와 허용 값은 `GET /projects/{projectId}/channels/{channelId}/fields`에서 확인한다.
- `text`, `keyword`, `aiField`는 문자열, `number`는 숫자, `select`는 옵션 key 또는 null, `multiSelect``images`는 배열, `date`는 날짜 문자열 또는 null을 사용한다.
- 응답에서도 채널 필드 값은 최상위 동적 속성으로 반환되며, `id`, `createdAt`, `updatedAt`, `issues`가 함께 제공된다.
- `issueNames`는 생성 요청에서만 사용하는 이슈 연결 제어 필드다.
- 장기적으로 SDK 호환성을 위해 `customFields` 래퍼를 도입할 수 있지만, 기존 클라이언트 호환성을 깨지 않도록 별도 버전 계약으로 진행한다.
## 5. 인증·권한·보안
- [ ] 공개 API Key를 프로젝트·채널 단위로 제한
- [ ] 관리자 JWT와 공개 API Key의 권한 차이 문서화
- [ ] Secretary API는 SSO JWT 검증을 필수화
- [ ] workspace·tenant·project 접근 범위 검증
- [ ] 비밀글 조회 권한을 API 레벨에서 강제
- [x] 내부 댓글·내부 메모가 일반 사용자 응답에 포함되지 않도록 보장
- [ ] R2 presigned URL 만료 시간 문서화
- [ ] API Key·JWT·R2 credential 로그 마스킹 확인
- [ ] rate limit과 업로드 용량 제한 추가
- [ ] CORS와 외부 패키지 허용 origin 정책 정의
### 보안 검증 기록
- 공개 API의 일반 댓글 목록·생성·첨부파일 접근은 내부 메모를 포함하지 않도록 고정했다. `is_internal` 입력과 `includeInternal` 조회 플래그는 공개 API에서 무시하며, 내부 메모는 관리자 전용 API에서만 처리한다.
- 공개 API Key는 `projectId` 기준으로 검증하고, `MASTER_API_KEY`는 서비스 간 내부 호출을 위한 전역 키로 구분했다.
- 관리자 API는 JWT guard와 권한 guard를 사용하고, Secretary API는 SSO JWT의 서명·만료·`sso_sub`·`tenant_id`를 검증한다.
## 6. Swagger/OpenAPI 산출물 및 배포
- [x] 공개 API OpenAPI JSON 생성
- [x] 관리자 API OpenAPI JSON 생성
- [x] Secretary API OpenAPI JSON export (`/openapi.json` 자동 산출)
- [ ] OpenAPI 파일을 패키지에 포함할지 결정
- [ ] Swagger UI를 staging에서만 노출할지 결정
- [ ] production에서는 인증된 관리자만 Swagger 접근 가능하도록 제한
- [ ] API 버전별 문서 URL 제공
- [ ] Postman collection 또는 SDK 생성 여부 결정
- [ ] 외부 패키지 배포 시 changelog와 breaking change 정책 추가
생성 산출물 후보:
packages/api-contract/openapi/public.json
packages/api-contract/openapi/admin.json
packages/api-contract/openapi/secretary.json
## 7. 테스트 계획
### 계약 테스트
- [ ] OpenAPI schema validation
- [ ] 요청 DTO validation
- [ ] 응답 DTO serialization
- [x] 오류 응답 형식 검증
- [ ] 페이지네이션·정렬·검색 검증
- [ ] 비밀글 권한 검증
- [ ] 내부 댓글 노출 차단 검증
### 기능 테스트
- [ ] 사용자 피드백 생성
- [ ] 피드백 목록·상세·수정·삭제
- [ ] 댓글 CRUD
- [ ] 내부 메모 CRUD
- [ ] 첨부파일 R2 업로드·다운로드·삭제
- [ ] 피드백 담당자 지정
- [ ] 상태 변경
- [ ] 이슈 연결·해제
- [ ] Gitea 이슈 생성·동기화
- [ ] 통합 대시보드 조회
- [ ] 다중 프로젝트 권한 필터링
- [ ] SSO 재로그인 후 사용자 정보 유지
### 배포 전 확인
- [ ] pnpm typecheck
- [ ] pnpm lint
- [ ] API 단위 테스트
- [ ] Secretary API 테스트
- [ ] E2E 테스트
- [ ] staging에서 OpenAPI JSON 확인
- [ ] 실제 외부 클라이언트 샘플 호출
- [ ] 마이그레이션 및 기존 데이터 호환성 확인
## 8. 권장 작업 순서
1. 공개 API와 관리자 API의 패키징 범위 확정
2. API 버전과 인증 정책 확정
3. 통합 대시보드 응답 DTO 작성
4. 피드백·댓글·내부메모·첨부파일 DTO 작성
5. 상태·우선순위·담당자 enum 정리
6. Swagger decorator 보완
7. Next.js 내부 프록시와 공식 API 경계 정리
8. OpenAPI JSON을 별도 계약 패키지로 export
9. 계약 테스트와 권한 테스트 추가
10. staging 배포 및 외부 호출 검증
11. SDK 또는 Postman collection 생성
12. API 패키지 버전 태그 및 배포
## 9. 완료 기준
- 외부 시스템이 Swagger 문서만 보고 피드백을 생성·조회·수정할 수 있다.
- 관리자 시스템이 피드백 상태·담당자·댓글·내부 메모·이슈·Gitea를 API로 처리할 수 있다.
- 사용자·관리자·내부 API의 권한이 분리되어 있다.
- 동적 필드와 비밀글 정책이 문서화되어 있다.
- 모든 주요 API의 요청·응답·오류 스키마가 OpenAPI에 표시된다.
- Swagger JSON이 CI에서 생성되고 버전 관리 또는 패키지 산출물로 배포된다.
- 기존 화면과 API 소비처의 호환성이 깨지지 않는다.
## 참고 문서
- SSOT 피드백 재구성 작업: docs/ssot-feedback-rearchitecture-tasks.md
- 다중 프로젝트 관리자 통합 대시보드 설계: docs/multi-project-admin-dashboard-design.md
- Secretary SSO 구성 설계: docs/architecture_secretary_sso_components_v2.md
- Q&A 플랫폼 작업 목록: docs/qna-platform-prototype-2-feedback-tasks.md
- NestJS API README: apps/api/README.md
+308
View File
@@ -0,0 +1,308 @@
# 통합 지원 플랫폼 상세 설계서
## 1. 프로젝트 개요
### 1.1 프로젝트 정보
| 항목 | 내용 |
| --- | --- |
| 플랫폼명 | BARON Q&A System |
| 핵심 목적 | EG-BIM 등 복수 소프트웨어의 Q&A, FAQ, 원격 지원 기능을 통합하고 BARON-SSO 기반 보안을 적용한 전사 기술지원 허브 구축 |
| 주요 대상 | BARON-SSO에 등록된 일반 사용자(User), 소프트웨어 담당자(Support), 시스템 관리자(Admin) |
### 1.2 추진 배경 및 기대 효과
- 여러 제품군에 분산된 기술지원 채널의 단일 플랫폼 통합
- 사용자 질문, FAQ, 원격 지원 이력의 일원화를 통한 대응 품질 및 추적성 향상
- BARON-SSO와 앱 단위 권한 제어를 통한 보안성 및 운영 효율 확보
## 2. 기술 아키텍처
### 2.1 기술 스택
| 구분 | 기술 |
| --- | --- |
| Frontend | Next.js, React, Tailwind CSS, Headless UI |
| Backend | FastAPI, Python, SQLAlchemy, Pydantic |
| Database | PostgreSQL |
| 인증/권한 | BARON-SSO, OAuth 2.0, OpenID Connect |
| 외부 연동 | ABC User Feedback 웹훅, 네이버웍스 알림 연동 |
| 인프라 | Ubuntu 24.04 (WSL2), Docker Compose, Nginx |
### 2.2 아키텍처 방향
- 프론트엔드의 Next.js 기반 구성으로 사용자 경험 및 생산성 확보
- 백엔드의 FastAPI 중심 경량 API 구조 설계를 통한 인증, 게시판, FAQ, 원격 지원 기능 분리 구현
- PostgreSQL과 SQLAlchemy 기반 데이터 계층 구성 및 앱별 접근 제어의 데이터 모델 반영
- BARON-SSO 연동 전제의 인증 구조 적용 및 표준 OAuth 2.0 / OIDC 기반 세션·토큰 검증 수행
- ABC User Feedback 연계를 통한 사용자 의견 수집 및 지원 품질 개선 체계 확보
- ABC User Feedback 웹훅 이벤트와 네이버웍스 알림 연계를 통한 실시간 커뮤니케이션 체계 확보
### 2.3 CI/CD 및 배포 프로세스
```mermaid
graph TD
A[개발자: 코드 작성 및 로컬 테스트] --> B[Main 브랜치 Push]
B --> C{GitHub Actions}
C --> D[Lint 및 Unit Test 실행]
D --> E[Docker Image 빌드]
E --> F[Container Registry 저장]
F --> G[운영 서버 배포]
G --> H[Nginx Proxy 라우팅]
H --> I[서비스 가동 및 모니터링]
```
## 3. 데이터베이스 설계
### 3.1 설계 원칙
권한 기반 데이터 격리(RBAC)를 위해 모든 게시물이 `app_id`를 참조하고, 사용자는 `user_app_access`를 통해 허용된 앱에만 접근하도록 설계
- 신규 앱이 추가되더라도 공통 스키마 변경 없이 `software_apps` 데이터 추가만으로 확장 가능한 구조 적용
- 전역 관리자 권한과 앱별 운영 권한 분리를 통한 멀티앱 확장 대응
- 사용자-앱 매핑의 중복 방지 및 상태값 표준화를 통한 운영 일관성 확보
- 목록 조회 성능 확보를 위한 앱 기준 인덱스 설계 반영
### 3.2 핵심 스키마
```sql
-- 1. 소프트웨어 정보
CREATE TABLE software_apps (
id SERIAL PRIMARY KEY,
app_code VARCHAR(50) UNIQUE NOT NULL,
app_name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2. 사용자 정보 (BARON-SSO 동기화)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
sso_user_id VARCHAR(100) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100),
company VARCHAR(100),
department VARCHAR(100),
global_role VARCHAR(20) DEFAULT 'USER', -- ADMIN, USER
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 3. 사용자별 앱 접근 권한 및 앱별 역할
CREATE TABLE user_app_access (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
app_id INTEGER NOT NULL REFERENCES software_apps(id),
app_role VARCHAR(20) NOT NULL DEFAULT 'USER', -- SUPPORT, USER
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, app_id)
);
-- 4. Q&A 상태 코드
CREATE TABLE qna_status_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 5. Q&A 카테고리 코드
CREATE TABLE qna_category_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 6. Q&A 게시글
CREATE TABLE qna_posts (
id SERIAL PRIMARY KEY,
app_id INTEGER NOT NULL REFERENCES software_apps(id),
user_id INTEGER NOT NULL REFERENCES users(id),
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
category_code VARCHAR(20) REFERENCES qna_category_codes(code),
status_code VARCHAR(20) NOT NULL DEFAULT 'RECEIVED' REFERENCES qna_status_codes(code),
is_secret BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 7. 댓글
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
post_id INTEGER NOT NULL REFERENCES qna_posts(id),
author_id INTEGER NOT NULL REFERENCES users(id),
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 8. 첨부파일 통합 관리
CREATE TABLE attachments (
id SERIAL PRIMARY KEY,
parent_type VARCHAR(20) NOT NULL, -- 'POST' 또는 'COMMENT'
parent_id INTEGER NOT NULL,
app_id INTEGER NOT NULL REFERENCES software_apps(id),
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 9. 원격 지원 상태 코드
CREATE TABLE remote_support_status_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 10. 원격 지원 로그
CREATE TABLE remote_support (
id SERIAL PRIMARY KEY,
post_id INTEGER NOT NULL REFERENCES qna_posts(id),
scheduled_time TIMESTAMP,
status_code VARCHAR(20) REFERENCES remote_support_status_codes(code),
support_engineer_id INTEGER REFERENCES users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 11. 주요 조회 인덱스
CREATE INDEX idx_user_app_access_app_role ON user_app_access (app_id, app_role);
CREATE INDEX idx_qna_posts_app_status_created ON qna_posts (app_id, status_code, created_at DESC);
CREATE INDEX idx_qna_posts_user_created ON qna_posts (user_id, created_at DESC);
CREATE INDEX idx_comments_post_created ON comments (post_id, created_at DESC);
CREATE INDEX idx_attachments_app_parent ON attachments (app_id, parent_type, parent_id);
```
## 4. 권한 및 보안 설계
### 4.1 역할 기반 접근 제어(RBAC)
| 역할 | 접근 범위 | 주요 권한 |
| --- | --- | --- |
| Admin | 전사 전체 소프트웨어 | 전체 통계 조회, 모든 게시글 수정/삭제, FAQ 관리, 앱 권한 부여 |
| Support | 본인에게 할당된 소프트웨어 | 담당 앱 Q&A 답변, 원격 지원 시작, 상태 변경 |
| User | 본인이 사용 중인 소프트웨어 | Q&A 작성/조회, FAQ 검색, 원격 지원 신청 |
### 4.2 권한 검증 로직
- 모든 API 요청에서 JWT 토큰 기준 사용자 식별 정보 추출
- 전사 관리자 여부는 `users.global_role` 기준 확인
- 앱 단위 접근 권한과 역할은 `user_app_access.app_role` 기준 검증
- 요청 앱에 대한 권한이 없을 경우 `403 Forbidden` 반환
- 비밀글(`is_secret = true`)의 작성자 본인, 권한 있는 Support, Admin 한정 조회
## 5. 주요 기능 및 UI 설계
### 5.1 통합 Q&A 리스트
- 제품별, 상태별, 날짜별 필터 제공
- 접수중, 검토중, 패치예정, 해결완료 상태의 컬러 배지 구분을 통한 가시성 강화
- MS Q&A 및 EG-BIM 사례를 참고한 검색성·가독성 중심 리스트 구조 적용
### 5.2 지능형 FAQ 및 원격 지원
- 질문 작성 시 제목 키워드 기반 관련 FAQ 실시간 추천
- 게시글 내용이 복잡하거나 재현이 어려운 경우 담당자에 의한 원격 지원 세션 생성 및 링크 전달
- Q&A에서 원격 지원으로 자연스럽게 전환되는 단순 운영 흐름 구성
### 5.3 파일 및 이미지 업로드
- 게시글 본문 및 댓글에서 드래그 앤 드롭 방식의 이미지 첨부 지원
- `attachments` 테이블을 통한 게시글·댓글 출처 구분 및 보존·삭제 이력 관리
- 초기 저장소의 로컬 볼륨 또는 S3 호환 스토리지 기준 검토
### 5.4 웹훅 기반 알림 연동
- ABC User Feedback에서 제공하는 웹훅을 활용한 앱별 Q&A 이벤트 수신
- 각 앱 사용자의 Q&A 화면 접근 시 주요 공지 또는 신규 문의 현황 노출
- 사용자의 신규 Q&A 글 작성 시 담당자 대상 네이버웍스 알림 발송
- 담당자의 답변글 작성 시 작성자 대상 네이버웍스 알림 발송
- 알림 이벤트의 앱별 라우팅, 수신 대상 매핑, 발송 이력 관리 체계 구성
## 6. 역할별 사용 시나리오
### 6.1 일반 사용자(User) 시나리오
- 각 소프트웨어 프로그램 로그인
- Q&A 페이지 접근
- 해당 앱 기준 Q&A 리스트 노출 및 기존 문의 확인
- 신규 문의 글 작성
- 담당자의 답변글 등록 시 네이버웍스 알림 수신
- 알림 확인 후 Q&A 페이지에서 답변글 확인
```mermaid
flowchart LR
A[사용자 로그인] --> B[Q&A 페이지 접근]
B --> C[해당 앱 Q&A 리스트 확인]
C --> D[신규 문의 글 작성]
D --> E[담당자 답변 등록]
E --> F[네이버웍스 알림 수신]
F --> G[답변글 확인]
```
### 6.2 소프트웨어 담당자(Support) 시나리오
- 본인 담당 소프트웨어 관련 글만 노출 및 확인
- 신규 등록 글의 새글 표시 확인
- 문의 내용 검토 후 답변글 작성
- 처리 단계에 따른 상태값 변경
- 답변 등록 시 사용자 대상 네이버웍스 알림 발송
```mermaid
flowchart LR
A[담당 앱 글 목록 확인] --> B[새글 표시 확인]
B --> C[문의 내용 검토]
C --> D[답변글 작성]
D --> E[상태값 변경]
E --> F[사용자 대상 네이버웍스 알림 발송]
```
### 6.3 관리자(Admin) 시나리오
- 로컬 개발 환경에서 기능 개발 및 수정
- Git 저장소 업로드
- CI/CD 파이프라인을 통한 운영 서버 Docker 환경 배포
- 배포 결과 및 운영 상태 확인
```mermaid
flowchart LR
A[로컬 개발 및 수정] --> B[Git 저장소 업로드]
B --> C[CI/CD 파이프라인 실행]
C --> D[운영 서버 Docker 배포]
D --> E[배포 결과 및 운영 상태 확인]
```
## 7. 구현 로드맵
### 7.1 Phase 1. 핵심 인프라 구축
- [ ] Ubuntu 및 Docker 서버 환경 셋업
- [ ] BARON-SSO OAuth2 연동 및 사용자 매핑 로직 구현
- [ ] RBAC 기반 DB 스키마 생성 및 기초 API 개발
### 7.2 Phase 2. 통합 플랫폼 UI 개발
- [ ] 소프트웨어별 격리 게시판 및 통합 리스트 UI 구현
- [ ] 첨부파일 및 이미지 업로드 시스템 구축
- [ ] 원격 지원 신청 기능 및 관리자 대시보드 연동
### 7.3 Phase 3. 고도화 및 운영 최적화
- [ ] AI 기반 FAQ 자동 추천 엔진 탑재
- [ ] ABC User Feedback 웹훅 및 네이버웍스 알림 연동 고도화
- [ ] 이슈 해결 통계 및 제품 품질 인사이트 보고서 자동화
### 7.4 추후 개발 예정 기능
#### 7.4.1 ADC User Feedback 연계 확장 기능
- 앱 메타정보 동기화 기능: BARON-SSO 또는 ADC User Feedback에 등록된 앱 코드, 앱명, 사용 여부 등의 정보를 우리 플랫폼과 자동으로 맞추는 기능
- 상태 변경 이벤트 기반 네이버웍스 추가 알림 기능
- FAQ, 기존 문의, 공지사항 기반 중복 문의 사전 방지 기능
- 앱별 문의 건수, 처리량, 응답 시간 기준 통계 대시보드 기능
- 소프트웨어별 Q&A 딥링크 또는 임베드 연동 기능: 각 소프트웨어 내부에서 해당 앱의 Q&A 화면으로 바로 이동하거나, Q&A 일부 화면을 프로그램 내부에 직접 표시하는 기능
#### 7.4.2 적용 검토 기준
- ADC User Feedback 제공 API, 웹훅, 임베드 기능의 실제 지원 범위 확인
- SSO 앱 메타정보와 플랫폼 내부 운영 메타정보 간 동기화 가능 여부 확인
- 네이버웍스 알림 대상자 매핑 및 부서별 알림 정책 적용 가능 여부 확인
- 운영 복잡도 대비 활용 효과가 높은 기능 우선 적용
+517
View File
@@ -0,0 +1,517 @@
# 사내 지원 플랫폼 상세 설계서
## 1. 프로젝트 개요
### 1.1 프로젝트 정보
| 항목 | 내용 |
| --- | --- |
| 플랫폼명 | BARON Office Support System |
| 핵심 목적 | 기존 통합 Q&A 플랫폼을 공용 기반으로 재사용하되, 인트라넷 진입형 사내 지원 서비스로 확장하여 물품신청, 도서 신청, 출장 차량 신청, 비품 대여, 사내 공지 및 Q&A 기능을 통합한 전사 지원 허브 구축 |
| 주요 대상 | S/W별 Q&A를 이용하는 사내 사용자 및 외부 고객(User), 인트라넷형 사내 지원 서비스를 이용하는 사내 사용자(User), 부서 담당자(Support), 시스템 관리자(Admin) |
### 1.2 추진 배경 및 기대 효과
- 개별 메신저, 이메일, 구두 요청으로 분산된 사내 요청 채널의 단일 플랫폼 통합
- 신청, 승인, 배정, 반납, 이력 조회의 전 과정 추적성 확보
- BARON-SSO 기반 인증과 부서·서비스 단위 권한 제어를 통한 운영 효율 확보
- 신청 현황, 처리 지연, 자산 이용률의 데이터 기반 관리 체계 확보
- 기존 S/W별 Q&A 플랫폼과 동일한 공용 프레임워크 재사용을 통한 개발 및 운영 표준화 확보
- 사내 사용자와 외부 고객을 함께 수용하는 사용자 모델 및 다중 알림 채널 운영 체계 확보
## 2. 기술 아키텍처
### 2.1 기술 스택
| 구분 | 기술 |
| --- | --- |
| Frontend | Next.js, React, Tailwind CSS, Headless UI |
| Backend | FastAPI, Python, SQLAlchemy, Pydantic |
| Database | PostgreSQL |
| 인증/권한 | BARON-SSO, OAuth 2.0, OpenID Connect |
| 외부 연동 | 네이버웍스 알림, SMS 발송 서비스, 사내 자산/사용자 정보 연동 API |
| 인프라 | Ubuntu 24.04 (WSL2), Docker Compose, Nginx |
### 2.2 아키텍처 방향
- 프론트엔드의 Next.js 기반 구성으로 신청, 조회, 승인 화면의 일관된 사용자 경험 확보
- 백엔드의 FastAPI 중심 API 구조 설계를 통한 신청, 승인, 자산관리, Q&A 기능 분리 구현
- PostgreSQL과 SQLAlchemy 기반 데이터 계층 구성 및 요청 유형별 공통 처리 구조 반영
- BARON-SSO 연동 전제의 인증 구조 적용 및 표준 OAuth 2.0 / OIDC 기반 세션·토큰 검증 수행
- 신청 상태 변경과 승인 결과의 네이버웍스 실시간 알림 체계 확보
- 네이버웍스 계정 미보유자 또는 발송 실패 건에 대한 SMS 대체 알림 체계 확보
- 추후 서비스 유형 추가를 고려한 멀티서비스 확장형 모델 적용
- 기존 S/W별 Q&A 플랫폼과 동일한 애플리케이션 기반을 재사용하되, 앱 진입형 구조 대신 인트라넷 진입형 서비스 컨텍스트 구조 적용
### 2.3 플랫폼 진입 구조
- 기존 기술지원 플랫폼은 각 S/W 프로그램 로그인 이후 해당 S/W의 Q&A 화면으로 직접 진입하는 앱 컨텍스트 기반 구조 적용
- 사내 지원 플랫폼은 회사 인트라넷에서 진입한 뒤 서비스 유형별 메뉴를 선택하는 포털 진입형 구조 적용
- 두 플랫폼은 동일한 인증 체계, 공통 UI 프레임워크, 공통 게시판/신청 처리 엔진을 공유하되 진입 URL과 초기 컨텍스트만 다르게 구성
- 외부 진입 컨텍스트인 `app_id` 또는 `service_type_id`를 내부 공통 식별자인 `workspace_id`로 매핑하여 동일 DB 구조에서 처리
### 2.4 CI/CD 및 배포 프로세스
```mermaid
graph TD
A[개발자: 코드 작성 및 로컬 테스트] --> B[Main 브랜치 Push]
B --> C{GitHub Actions}
C --> D[Lint 및 Unit Test 실행]
D --> E[Docker Image 빌드]
E --> F[Container Registry 저장]
F --> G[운영 서버 배포]
G --> H[Nginx Proxy 라우팅]
H --> I[서비스 가동 및 모니터링]
```
## 3. 데이터베이스 설계
### 3.1 설계 원칙
기존 S/W별 Q&A 플랫폼과 인트라넷형 사내 지원 플랫폼을 동일 DB에서 운영하기 위해 모든 게시판·신청 데이터를 `workspace_id` 기준으로 통합 관리하도록 설계
- 외부 진입 채널은 달라도 내부 저장 구조는 공통 `workspace` 기준으로 통합 적용
- S/W별 Q&A와 사내 지원 요청은 공통 본문·댓글·첨부 구조를 공유하고, 승인·자산배정·원격지원 등은 도메인별 확장 테이블로 분리 적용
- 전역 관리자 권한과 워크스페이스별 운영 권한 분리를 통한 멀티플랫폼 확장 대응
- 신규 S/W 또는 신규 사내 서비스 추가 시 공통 스키마 변경 없이 마스터 데이터 추가만으로 확장 가능한 구조 적용
- 목록 조회 성능 확보를 위한 워크스페이스, 상태, 작성자 기준 인덱스 설계 반영
- 사내 사용자와 외부 고객을 모두 지원할 수 있도록 사용자 유형과 다중 알림 채널 정보 관리 구조 반영
### 3.2 핵심 스키마
```sql
-- 1. 소프트웨어 정보
CREATE TABLE software_apps (
id SERIAL PRIMARY KEY,
app_code VARCHAR(50) UNIQUE NOT NULL,
app_name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2. 사내 서비스 유형 정보
CREATE TABLE service_types (
id SERIAL PRIMARY KEY,
service_code VARCHAR(50) UNIQUE NOT NULL,
service_name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 3. 사용자 정보 (BARON-SSO 동기화)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
sso_user_id VARCHAR(100) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100),
company VARCHAR(100),
department VARCHAR(100),
user_type VARCHAR(20) NOT NULL DEFAULT 'INTERNAL', -- INTERNAL, EXTERNAL
phone_number VARCHAR(30),
naverworks_user_key VARCHAR(100),
global_role VARCHAR(20) DEFAULT 'USER', -- ADMIN, USER
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 4. 공통 워크스페이스 마스터
CREATE TABLE workspaces (
id SERIAL PRIMARY KEY,
workspace_type VARCHAR(20) NOT NULL, -- SOFTWARE_APP, INTRANET_SERVICE
software_app_id INTEGER REFERENCES software_apps(id),
service_type_id INTEGER REFERENCES service_types(id),
workspace_code VARCHAR(50) UNIQUE NOT NULL,
workspace_name VARCHAR(100) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHECK (
(workspace_type = 'SOFTWARE_APP' AND software_app_id IS NOT NULL AND service_type_id IS NULL)
OR
(workspace_type = 'INTRANET_SERVICE' AND service_type_id IS NOT NULL AND software_app_id IS NULL)
)
);
-- 5. 사용자별 워크스페이스 접근 권한 및 역할
CREATE TABLE user_workspace_access (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
workspace_role VARCHAR(20) NOT NULL DEFAULT 'USER', -- SUPPORT, USER
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, workspace_id)
);
-- 6. 공통 상태 코드
CREATE TABLE support_status_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 7. 공통 카테고리 코드
CREATE TABLE support_category_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 8. 공통 게시글/신청 본문
CREATE TABLE support_tickets (
id SERIAL PRIMARY KEY,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
requester_id INTEGER NOT NULL REFERENCES users(id),
ticket_type VARCHAR(20) NOT NULL, -- QNA, REQUEST
title VARCHAR(255) NOT NULL,
content TEXT,
category_code VARCHAR(20) REFERENCES support_category_codes(code),
status_code VARCHAR(20) NOT NULL DEFAULT 'OPEN' REFERENCES support_status_codes(code),
is_secret BOOLEAN DEFAULT FALSE,
requested_start_at TIMESTAMP,
requested_end_at TIMESTAMP,
priority VARCHAR(20) DEFAULT 'NORMAL',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 9. 댓글 및 처리 메모
CREATE TABLE ticket_comments (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
author_id INTEGER NOT NULL REFERENCES users(id),
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 10. 첨부파일 통합 관리
CREATE TABLE attachments (
id SERIAL PRIMARY KEY,
parent_type VARCHAR(20) NOT NULL, -- 'TICKET' 또는 'COMMENT'
parent_id INTEGER NOT NULL,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 11. 사내 지원용 승인 이력
CREATE TABLE request_approvals (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
approver_id INTEGER NOT NULL REFERENCES users(id),
approval_status VARCHAR(20) NOT NULL, -- APPROVED, REJECTED, PENDING
comment TEXT,
approved_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 12. 자산/비품 마스터
CREATE TABLE assets (
id SERIAL PRIMARY KEY,
asset_code VARCHAR(50) UNIQUE NOT NULL,
asset_name VARCHAR(100) NOT NULL,
asset_type VARCHAR(30) NOT NULL, -- SUPPLY, EQUIPMENT, BOOK, VEHICLE
quantity INTEGER DEFAULT 1,
is_active BOOLEAN DEFAULT TRUE,
location VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 13. 자산 배정 및 대여 이력
CREATE TABLE asset_allocations (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
asset_id INTEGER NOT NULL REFERENCES assets(id),
assignee_id INTEGER REFERENCES users(id),
allocation_status VARCHAR(20) NOT NULL, -- RESERVED, LOANED, RETURNED, CANCELLED
loaned_at TIMESTAMP,
due_at TIMESTAMP,
returned_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 14. 차량 운행 일정
CREATE TABLE vehicle_schedules (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
asset_id INTEGER NOT NULL REFERENCES assets(id),
departure_at TIMESTAMP NOT NULL,
arrival_at TIMESTAMP,
destination VARCHAR(255),
driver_name VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 15. S/W Q&A용 원격 지원 상태 코드
CREATE TABLE remote_support_status_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 16. S/W Q&A용 원격 지원 로그
CREATE TABLE remote_support (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
status_code VARCHAR(20) REFERENCES remote_support_status_codes(code),
support_engineer_id INTEGER REFERENCES users(id),
scheduled_time TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 17. 공통 FAQ
CREATE TABLE faqs (
id SERIAL PRIMARY KEY,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 18. 알림 발송 이력
CREATE TABLE notification_logs (
id SERIAL PRIMARY KEY,
ticket_id INTEGER REFERENCES support_tickets(id),
recipient_id INTEGER REFERENCES users(id),
channel VARCHAR(20) NOT NULL, -- NAVERWORKS, SMS
target_address VARCHAR(100),
delivery_status VARCHAR(20) NOT NULL, -- SUCCESS, FAILED, FALLBACK
fallback_channel VARCHAR(20),
error_message TEXT,
sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 19. 주요 조회 인덱스
CREATE INDEX idx_user_workspace_access_role ON user_workspace_access (workspace_id, workspace_role);
CREATE INDEX idx_support_tickets_workspace_status_created ON support_tickets (workspace_id, status_code, created_at DESC);
CREATE INDEX idx_support_tickets_requester_created ON support_tickets (requester_id, created_at DESC);
CREATE INDEX idx_ticket_comments_ticket_created ON ticket_comments (ticket_id, created_at DESC);
CREATE INDEX idx_asset_allocations_asset_status ON asset_allocations (asset_id, allocation_status);
CREATE INDEX idx_vehicle_schedules_asset_departure ON vehicle_schedules (asset_id, departure_at);
CREATE INDEX idx_notification_logs_ticket_sent ON notification_logs (ticket_id, sent_at DESC);
```
### 3.3 워크스페이스 구성 예시
| 워크스페이스 유형 | 코드 | 이름 | 주요 처리 내용 |
| --- | --- | --- | --- |
| SOFTWARE_APP | EG_BIM | EG-BIM Q&A | 해당 S/W 전용 Q&A 게시판 및 원격 지원 |
| SOFTWARE_APP | CIVIL_APP | Civil App Q&A | 해당 S/W 전용 문의 접수 및 답변 |
| INTRANET_SERVICE | SUPPLY_REQUEST | 물품신청 | 사무용품, 소모품, 구매 요청 |
| INTRANET_SERVICE | BOOK_REQUEST | 도서 신청 | 업무 도서 구매 요청 또는 사내 도서 배정 |
| INTRANET_SERVICE | VEHICLE_REQUEST | 출장 차량 신청 | 출장 일정 기반 차량 예약 및 배차 요청 |
| INTRANET_SERVICE | EQUIPMENT_RENTAL | 비품 대여 | 노트북, 빔프로젝터, 케이블 등 비품 대여 |
| INTRANET_SERVICE | OFFICE_QNA | 사내 Q&A | 총무, 시설, 복지 관련 문의 접수 및 답변 |
## 4. 권한 및 보안 설계
### 4.1 역할 기반 접근 제어(RBAC)
| 역할 | 접근 범위 | 주요 권한 |
| --- | --- | --- |
| Admin | 전사 전체 워크스페이스 | 전체 통계 조회, 모든 게시글/신청 수정·삭제, 권한 부여, 워크스페이스 관리 |
| Support | 본인 담당 워크스페이스 | 신청 승인/반려, 배정 처리, 상태 변경, FAQ 관리, 답변 작성 |
| User | 본인이 접근 가능한 워크스페이스 | 신청 작성, 문의 작성, 본인 내역 조회, 댓글 작성, FAQ 조회 |
### 4.2 권한 검증 로직
- 모든 API 요청에서 JWT 토큰 기준 사용자 식별 정보 추출
- 전사 관리자 여부는 `users.global_role` 기준 확인
- 워크스페이스 단위 접근 권한과 역할은 `user_workspace_access.workspace_role` 기준 검증
- 요청 워크스페이스에 대한 권한이 없을 경우 `403 Forbidden` 반환
- S/W별 Q&A 게시글과 사내 지원 신청은 모두 작성자 본인, 권한 있는 Support, Admin 한정 조회
- S/W별 Q&A 워크스페이스는 사내 사용자(`INTERNAL`)와 외부 고객(`EXTERNAL`) 모두 접근 가능하도록 설계하고, 인트라넷형 서비스 워크스페이스는 사내 사용자 중심으로 제한 적용
## 5. 주요 기능 및 UI 설계
### 5.1 공통 워크스페이스 리스트
- 워크스페이스별, 상태별, 날짜별 필터 제공
- S/W별 Q&A와 인트라넷형 사내 지원 서비스를 하나의 공통 리스트 엔진으로 구성
- 사용자 진입 컨텍스트에 따라 해당 워크스페이스 화면만 우선 노출하는 구조 적용
- 문의, 요청, 처리중, 완료, 반려 등 상태값의 컬러 배지 구분을 통한 가시성 강화
### 5.2 S/W별 Q&A 기능
- 각 S/W 로그인 이후 해당 앱 전용 Q&A 게시판으로 직접 진입하는 앱 컨텍스트 기반 게시판 기능
- 제품별 문의 작성, 댓글 답변, 비밀글, 상태 변경 기능
- 질문 작성 시 관련 FAQ 추천 및 기존 문의 검색 기능
- 복잡한 문의에 대한 원격 지원 전환 및 지원 이력 관리 기능
- 앱별 공지, 신규 문의 현황, 담당자 답변 상태 표시 기능
### 5.3 인트라넷형 사내 지원 기능
- 물품신청: 품목, 수량, 사용 목적 입력 기반 신청 기능
- 도서 신청: 도서명, 저자, 출판사, 신청 사유 입력 기반 신청 기능
- 출장 차량 신청: 출장 일정, 목적지, 탑승 인원 기반 차량 신청 기능
- 비품 대여: 대여 품목, 사용 기간, 반납 예정일 입력 기반 대여 신청 기능
- 사내 Q&A: 총무, 시설, 복지, 기타 운영 문의 작성 및 답변 기능
### 5.4 승인, 배정 및 원격 처리
- 사내 지원 요청에 대한 담당자 승인 또는 반려 처리 기능
- 차량, 도서, 비품 등 실제 자산 배정 처리 기능
- 반려 사유, 처리 메모, 배정 결과의 이력 관리 기능
- 반납 완료 시 상태 자동 갱신 및 이력 보존 기능
- S/W별 Q&A 문의에 대한 원격 지원 일정 등록 및 처리 상태 관리 기능
### 5.5 FAQ, 공지 및 추천 기능
- 워크스페이스별 FAQ 및 공지사항 분리 관리 기능
- 질문 또는 신청 작성 시 관련 FAQ 및 기존 공지사항 추천 기능
- 자주 발생하는 요청 유형과 반복 문의의 사전 안내를 통한 중복 문의 감소 기능
- S/W별 앱 FAQ와 인트라넷형 서비스 안내 문서를 동일한 추천 구조로 제공
### 5.6 알림 및 이벤트 연동
- S/W별 Q&A 신규 문의 등록 시 담당자 대상 네이버웍스 알림 발송
- S/W별 Q&A 답변 등록 시 작성자 대상 네이버웍스 알림 발송
- 사내 지원 신청 등록 시 담당자 대상 네이버웍스 알림 발송
- 승인, 반려, 배정, 반납 처리 시 신청자 대상 네이버웍스 알림 발송
- 처리 지연 건 발생 시 담당자 리마인드 알림 발송
- 워크스페이스 유형별 알림 대상 라우팅 및 발송 이력 관리 체계 구성
- 네이버웍스 계정이 없는 사용자 또는 네이버웍스 발송 실패 건에 대한 SMS 대체 알림 발송 기능
- 알림 발송 채널, 실패 사유, 대체 발송 결과를 `notification_logs` 기준으로 추적 관리
## 6. 역할별 사용 시나리오
### 6.1 S/W 사용자(User) 시나리오
- 각 S/W 프로그램 로그인
- 사내 사용자 또는 외부 고객 자격으로 해당 앱 접근
- 해당 앱의 Q&A 화면으로 직접 진입
- 해당 앱 기준 Q&A 리스트 노출 및 기존 문의 확인
- 신규 문의 글 작성
- 담당자의 답변글 등록 시 네이버웍스 알림 수신 또는 SMS 대체 알림 수신
- 알림 확인 후 해당 앱 Q&A 화면에서 답변글 확인
```mermaid
flowchart LR
A[사내 사용자 또는 외부 고객 로그인] --> B[해당 S/W Q&A 진입]
B --> C[해당 앱 Q&A 리스트 확인]
C --> D[신규 문의 글 작성]
D --> E[담당자 답변 등록]
E --> F[네이버웍스 또는 SMS 알림 수신]
F --> G[답변글 확인]
```
### 6.2 S/W 담당자(Support) 시나리오
- 본인 담당 S/W 워크스페이스 접근
- 신규 문의 및 새글 표시 확인
- 문의 내용 검토 후 답변글 작성
- 상태값 변경 또는 원격 지원 전환 처리
- 작성자 대상 알림 발송
```mermaid
flowchart LR
A[담당 S/W 워크스페이스 접근] --> B[신규 문의 확인]
B --> C[문의 내용 검토]
C --> D[답변 또는 원격 지원 처리]
D --> E[상태값 변경]
E --> F[사용자 대상 알림 발송]
```
### 6.3 인트라넷 사용자(User) 시나리오
- 회사 인트라넷 접속 및 BARON-SSO 로그인
- 신청 서비스 선택
- 물품신청, 도서 신청, 출장 차량 신청, 비품 대여 또는 사내 Q&A 작성
- 처리 상태 및 승인 결과 확인
- 네이버웍스 알림 수신 후 상세 내역 확인
```mermaid
flowchart LR
A[인트라넷 접속 및 로그인] --> B[신청 서비스 선택]
B --> C[신청서 또는 문의 작성]
C --> D[담당자 처리 진행]
D --> E[승인 또는 반려 결과 확정]
E --> F[네이버웍스 알림 수신]
F --> G[상세 내역 확인]
```
### 6.4 인트라넷 담당자(Support) 시나리오
- 본인 담당 서비스 워크스페이스 목록 확인
- 신규 신청 또는 문의 접수 확인
- 승인, 반려, 배정, 답변 처리 수행
- 상태값 변경 및 처리 메모 등록
- 신청자 대상 알림 발송
```mermaid
flowchart LR
A[담당 서비스 워크스페이스 확인] --> B[신규 요청 접수 확인]
B --> C[승인 또는 반려 검토]
C --> D[배정 또는 답변 처리]
D --> E[상태값 변경 및 메모 등록]
E --> F[신청자 대상 알림 발송]
```
### 6.5 관리자(Admin) 시나리오
- 로컬 개발 환경에서 기능 개발 및 수정
- Git 저장소 업로드
- CI/CD 파이프라인을 통한 운영 서버 Docker 환경 배포
- S/W 앱 마스터, 서비스 유형, 워크스페이스 권한, 자산 마스터 관리
- 배포 결과 및 운영 상태 확인
```mermaid
flowchart LR
A[로컬 개발 및 수정] --> B[Git 저장소 업로드]
B --> C[CI/CD 파이프라인 실행]
C --> D[운영 서버 Docker 배포]
D --> E[앱/서비스/권한/자산 마스터 관리]
E --> F[운영 상태 확인]
```
## 7. 구현 로드맵
### 7.1 Phase 1. 핵심 인프라 구축
- [ ] Ubuntu 및 Docker 서버 환경 셋업
- [ ] BARON-SSO OAuth2 연동 및 사용자 매핑 로직 구현
- [ ] 공용 워크스페이스 기반 DB 스키마 생성 및 기초 API 개발
- [ ] S/W별 Q&A와 인트라넷형 서비스의 공통 인증 및 권한 구조 구현
### 7.2 Phase 2. S/W별 Q&A 및 공용 게시판 기능 개발
- [ ] S/W별 격리 게시판 및 통합 리스트 UI 구현
- [ ] 댓글, 첨부파일, 비밀글, FAQ 추천 기능 구현
- [ ] 원격 지원 전환 및 상태 관리 기능 구축
- [ ] 네이버웍스 알림, SMS 대체 알림 및 앱별 컨텍스트 진입 기능 구현
### 7.3 Phase 3. 인트라넷형 사내 지원 기능 개발
- [ ] 서비스 유형별 신청서 UI 및 통합 리스트 구현
- [ ] 승인/반려/배정 처리 기능 구현
- [ ] 자산 및 차량 일정 관리 기능 구축
- [ ] 첨부파일 및 댓글 시스템 구축
### 7.4 Phase 4. 고도화 및 운영 최적화
- [ ] FAQ 추천 및 중복 문의·중복 신청 사전 방지 기능 탑재
- [ ] 네이버웍스 알림, SMS 대체 발송, 처리 지연 리마인드 기능 고도화
- [ ] S/W별 Q&A 통계와 사내 지원 서비스 운영 인사이트 보고서 자동화
### 7.5 추후 개발 예정 기능
#### 7.5.1 공용 플랫폼 확장 기능
- 조직도 기반 결재선 자동 추천 기능: 신청 유형과 부서 기준으로 결재 대상 자동 추천 기능
- 자산 메타정보 동기화 기능: 사내 자산관리 시스템 또는 외부 마스터 정보와 비품, 차량, 도서 정보를 자동 동기화하는 기능
- 도서 및 비품 재고 예측 기능: 사용량 기반 부족 품목 예측 및 사전 구매 추천 기능
- 서비스별 맞춤 대시보드 기능: 부서별 처리량, 반려율, 평균 승인 시간 시각화 기능
- 앱 메타정보 동기화 기능: BARON-SSO 또는 외부 시스템에 등록된 S/W 정보를 공용 플랫폼과 자동으로 맞추는 기능
- S/W별 Q&A 딥링크 또는 임베드 연동 기능: 각 소프트웨어 내부에서 해당 앱의 Q&A 화면으로 바로 이동하거나 일부 화면을 직접 표시하는 기능
- 사내 포털 딥링크 또는 임베드 연동 기능: 그룹웨어 또는 사내 포털에서 해당 서비스 화면으로 바로 이동하거나 일부 화면을 직접 표시하는 기능
#### 7.5.2 적용 검토 기준
- 사내 자산관리, 그룹웨어, 조직도 API의 실제 연동 가능 범위 확인
- BARON-SSO 또는 외부 시스템의 앱 메타정보 동기화 가능 범위 확인
- 결재 정책과 운영 프로세스의 시스템 반영 가능 여부 확인
- 네이버웍스 알림 대상자 및 부서별 알림 정책 적용 가능 여부 확인
- 외부 고객 대상 SMS 발송 정책 및 개인정보 보관 기준 적용 가능 여부 확인
- 운영 복잡도 대비 활용 효과가 높은 기능 우선 적용
+527
View File
@@ -0,0 +1,527 @@
# 사내 지원 플랫폼 상세 설계서
## 1. 프로젝트 개요
### 1.1 프로젝트 정보
| 항목 | 내용 |
| --- | --- |
| 플랫폼명 | BARON Office Support System |
| 핵심 목적 | 기존 통합 Q&A 플랫폼을 공용 기반으로 재사용하되, 인트라넷 진입형 사내 지원 서비스로 확장하여 물품신청, 도서 신청, 출장 차량 신청, 비품 대여, 사내 공지 및 Q&A 기능을 통합한 전사 지원 허브 구축 |
| 주요 대상 | S/W별 Q&A를 이용하는 사내 사용자 및 외부 고객(User), 인트라넷형 사내 지원 서비스를 이용하는 사내 사용자(User), 부서 담당자(Support), 시스템 관리자(Admin) |
### 1.2 추진 배경 및 기대 효과
- 개별 메신저, 이메일, 구두 요청으로 분산된 사내 요청 채널의 단일 플랫폼 통합
- 신청, 승인, 배정, 반납, 이력 조회의 전 과정 추적성 확보
- BARON-SSO 기반 인증과 부서·서비스 단위 권한 제어를 통한 운영 효율 확보
- 신청 현황, 처리 지연, 자산 이용률의 데이터 기반 관리 체계 확보
- 기존 S/W별 Q&A 플랫폼과 동일한 공용 프레임워크 재사용을 통한 개발 및 운영 표준화 확보
- 사내 사용자와 외부 고객을 함께 수용하는 사용자 모델 및 다중 알림 채널 운영 체계 확보
## 2. 기술 아키텍처
### 2.1 기술 스택
| 구분 | 기술 |
| --- | --- |
| Frontend | Next.js, React, Tailwind CSS, Headless UI |
| Backend | FastAPI, Python, SQLAlchemy, Pydantic |
| Database | PostgreSQL |
| 인증/권한 | BARON-SSO, OAuth 2.0, OpenID Connect |
| 외부 연동 | 네이버웍스 알림, SMS 발송 서비스, 사내 자산/사용자 정보 연동 API |
| 인프라 | Ubuntu 24.04 (WSL2), Docker Compose, Nginx |
### 2.2 아키텍처 방향
- 프론트엔드의 Next.js 기반 구성으로 신청, 조회, 승인 화면의 일관된 사용자 경험 확보
- 백엔드의 FastAPI 중심 API 구조 설계를 통한 신청, 승인, 자산관리, Q&A 기능 분리 구현
- PostgreSQL과 SQLAlchemy 기반 데이터 계층 구성 및 요청 유형별 공통 처리 구조 반영
- BARON-SSO 연동 전제의 인증 구조 적용 및 표준 OAuth 2.0 / OIDC 기반 세션·토큰 검증 수행
- 신청 상태 변경과 승인 결과의 네이버웍스 실시간 알림 체계 확보
- 네이버웍스 계정 미보유자 또는 발송 실패 건에 대한 SMS 대체 알림 체계 확보
- 추후 서비스 유형 추가를 고려한 멀티서비스 확장형 모델 적용
- 기존 S/W별 Q&A 플랫폼과 동일한 애플리케이션 기반을 재사용하되, 앱 진입형 구조 대신 인트라넷 진입형 서비스 컨텍스트 구조 적용
### 2.3 플랫폼 진입 구조
- 기존 기술지원 플랫폼은 각 S/W 프로그램 로그인 이후 해당 S/W의 Q&A 화면으로 직접 진입하는 앱 컨텍스트 기반 구조 적용
- 사내 지원 플랫폼은 회사 인트라넷에서 진입한 뒤 서비스 유형별 메뉴를 선택하는 포털 진입형 구조 적용
- 두 플랫폼은 동일한 인증 체계, 공통 UI 프레임워크, 공통 게시판/신청 처리 엔진을 공유하되 진입 URL과 초기 컨텍스트만 다르게 구성
- 외부 진입 컨텍스트인 `app_id` 또는 `service_type_id`를 내부 공통 식별자인 `workspace_id`로 매핑하여 동일 DB 구조에서 처리
### 2.4 CI/CD 및 배포 프로세스
```mermaid
graph TD
A[개발자: 코드 작성 및 로컬 테스트] --> B[Main 브랜치 Push]
B --> C{GitHub Actions}
C --> D[Lint 및 Unit Test 실행]
D --> E[Docker Image 빌드]
E --> F[Container Registry 저장]
F --> G[운영 서버 배포]
G --> H[Nginx Proxy 라우팅]
H --> I[서비스 가동 및 모니터링]
```
## 3. 데이터베이스 설계
### 3.1 설계 원칙
기존 S/W별 Q&A 플랫폼과 인트라넷형 사내 지원 플랫폼을 동일 DB에서 운영하기 위해 모든 게시판·신청 데이터를 `workspace_id` 기준으로 통합 관리하도록 설계
- 외부 진입 채널은 달라도 내부 저장 구조는 공통 `workspace` 기준으로 통합 적용
- S/W별 Q&A와 사내 지원 요청은 공통 본문·댓글·첨부 구조를 공유하고, 승인·자산배정·원격지원 등은 도메인별 확장 테이블로 분리 적용
- 전역 관리자 권한과 워크스페이스별 운영 권한 분리를 통한 멀티플랫폼 확장 대응
- 신규 S/W 또는 신규 사내 서비스 추가 시 공통 스키마 변경 없이 마스터 데이터 추가만으로 확장 가능한 구조 적용
- 목록 조회 성능 확보를 위한 워크스페이스, 상태, 작성자 기준 인덱스 설계 반영
- 사용자 기본 정보와 tenant 정보는 BARON-SSO를 원본으로 사용하고, 플랫폼 내부에서는 `user_id`, `tenant_id` 및 권한 정보만 관리하는 구조 반영
### 3.2 핵심 스키마
```sql
-- 1. 소프트웨어 정보
CREATE TABLE software_apps (
id SERIAL PRIMARY KEY,
app_code VARCHAR(50) UNIQUE NOT NULL,
app_name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2. 사내 서비스 유형 정보
CREATE TABLE service_types (
id SERIAL PRIMARY KEY,
service_code VARCHAR(50) UNIQUE NOT NULL,
service_name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 3. 공통 워크스페이스 마스터
CREATE TABLE workspaces (
id SERIAL PRIMARY KEY,
workspace_type VARCHAR(20) NOT NULL, -- SOFTWARE_APP, INTRANET_SERVICE
software_app_id INTEGER REFERENCES software_apps(id),
service_type_id INTEGER REFERENCES service_types(id),
workspace_code VARCHAR(50) UNIQUE NOT NULL,
workspace_name VARCHAR(100) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHECK (
(workspace_type = 'SOFTWARE_APP' AND software_app_id IS NOT NULL AND service_type_id IS NULL)
OR
(workspace_type = 'INTRANET_SERVICE' AND service_type_id IS NOT NULL AND software_app_id IS NULL)
)
);
-- 4. 사용자별 워크스페이스 접근 권한 및 역할
CREATE TABLE user_workspace_access (
id SERIAL PRIMARY KEY,
user_id VARCHAR(100) NOT NULL,
tenant_id VARCHAR(100) NOT NULL,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
workspace_role VARCHAR(20) NOT NULL DEFAULT 'USER', -- SUPPORT, USER
can_read BOOLEAN DEFAULT TRUE,
can_write BOOLEAN DEFAULT FALSE,
can_manage BOOLEAN DEFAULT FALSE,
can_approve BOOLEAN DEFAULT FALSE,
page_scope VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, tenant_id, workspace_id)
);
-- 5. 사용자 알림 보조 정보
CREATE TABLE user_notification_profiles (
id SERIAL PRIMARY KEY,
user_id VARCHAR(100) NOT NULL,
tenant_id VARCHAR(100) NOT NULL,
user_type VARCHAR(20) NOT NULL DEFAULT 'INTERNAL', -- INTERNAL, EXTERNAL
phone_number VARCHAR(30),
naverworks_user_key VARCHAR(100),
sms_opt_in BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, tenant_id)
);
-- 6. 공통 상태 코드
CREATE TABLE support_status_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 7. 공통 카테고리 코드
CREATE TABLE support_category_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 8. 공통 게시글/신청 본문
CREATE TABLE support_tickets (
id SERIAL PRIMARY KEY,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
requester_id VARCHAR(100) NOT NULL,
requester_tenant_id VARCHAR(100) NOT NULL,
ticket_type VARCHAR(20) NOT NULL, -- QNA, REQUEST
title VARCHAR(255) NOT NULL,
content TEXT,
category_code VARCHAR(20) REFERENCES support_category_codes(code),
status_code VARCHAR(20) NOT NULL DEFAULT 'OPEN' REFERENCES support_status_codes(code),
is_secret BOOLEAN DEFAULT FALSE,
requested_start_at TIMESTAMP,
requested_end_at TIMESTAMP,
priority VARCHAR(20) DEFAULT 'NORMAL',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 9. 댓글 및 처리 메모
CREATE TABLE ticket_comments (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
author_id VARCHAR(100) NOT NULL,
author_tenant_id VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 10. 첨부파일 통합 관리
CREATE TABLE attachments (
id SERIAL PRIMARY KEY,
parent_type VARCHAR(20) NOT NULL, -- 'TICKET' 또는 'COMMENT'
parent_id INTEGER NOT NULL,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 11. 사내 지원용 승인 이력
CREATE TABLE request_approvals (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
approver_id VARCHAR(100) NOT NULL,
approver_tenant_id VARCHAR(100) NOT NULL,
approval_status VARCHAR(20) NOT NULL, -- APPROVED, REJECTED, PENDING
comment TEXT,
approved_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 12. 자산/비품 마스터
CREATE TABLE assets (
id SERIAL PRIMARY KEY,
asset_code VARCHAR(50) UNIQUE NOT NULL,
asset_name VARCHAR(100) NOT NULL,
asset_type VARCHAR(30) NOT NULL, -- SUPPLY, EQUIPMENT, BOOK, VEHICLE
quantity INTEGER DEFAULT 1,
is_active BOOLEAN DEFAULT TRUE,
location VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 13. 자산 배정 및 대여 이력
CREATE TABLE asset_allocations (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
asset_id INTEGER NOT NULL REFERENCES assets(id),
assignee_id VARCHAR(100),
assignee_tenant_id VARCHAR(100),
allocation_status VARCHAR(20) NOT NULL, -- RESERVED, LOANED, RETURNED, CANCELLED
loaned_at TIMESTAMP,
due_at TIMESTAMP,
returned_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 14. 차량 운행 일정
CREATE TABLE vehicle_schedules (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
asset_id INTEGER NOT NULL REFERENCES assets(id),
departure_at TIMESTAMP NOT NULL,
arrival_at TIMESTAMP,
destination VARCHAR(255),
driver_name VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 15. S/W Q&A용 원격 지원 상태 코드
CREATE TABLE remote_support_status_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 16. S/W Q&A용 원격 지원 로그
CREATE TABLE remote_support (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
status_code VARCHAR(20) REFERENCES remote_support_status_codes(code),
support_engineer_id VARCHAR(100),
support_engineer_tenant_id VARCHAR(100),
scheduled_time TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 17. 공통 FAQ
CREATE TABLE faqs (
id SERIAL PRIMARY KEY,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 18. 알림 발송 이력
CREATE TABLE notification_logs (
id SERIAL PRIMARY KEY,
ticket_id INTEGER REFERENCES support_tickets(id),
recipient_id VARCHAR(100) NOT NULL,
recipient_tenant_id VARCHAR(100) NOT NULL,
channel VARCHAR(20) NOT NULL, -- NAVERWORKS, SMS
target_address VARCHAR(100),
delivery_status VARCHAR(20) NOT NULL, -- SUCCESS, FAILED, FALLBACK
fallback_channel VARCHAR(20),
error_message TEXT,
sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 19. 주요 조회 인덱스
CREATE INDEX idx_user_workspace_access_role ON user_workspace_access (workspace_id, workspace_role);
CREATE INDEX idx_support_tickets_workspace_status_created ON support_tickets (workspace_id, status_code, created_at DESC);
CREATE INDEX idx_support_tickets_requester_created ON support_tickets (requester_id, created_at DESC);
CREATE INDEX idx_ticket_comments_ticket_created ON ticket_comments (ticket_id, created_at DESC);
CREATE INDEX idx_asset_allocations_asset_status ON asset_allocations (asset_id, allocation_status);
CREATE INDEX idx_vehicle_schedules_asset_departure ON vehicle_schedules (asset_id, departure_at);
CREATE INDEX idx_notification_logs_ticket_sent ON notification_logs (ticket_id, sent_at DESC);
```
### 3.3 워크스페이스 구성 예시
| 워크스페이스 유형 | 코드 | 이름 | 주요 처리 내용 |
| --- | --- | --- | --- |
| SOFTWARE_APP | EG_BIM | EG-BIM Q&A | 해당 S/W 전용 Q&A 게시판 및 원격 지원 |
| SOFTWARE_APP | CIVIL_APP | Civil App Q&A | 해당 S/W 전용 문의 접수 및 답변 |
| INTRANET_SERVICE | SUPPLY_REQUEST | 물품신청 | 사무용품, 소모품, 구매 요청 |
| INTRANET_SERVICE | BOOK_REQUEST | 도서 신청 | 업무 도서 구매 요청 또는 사내 도서 배정 |
| INTRANET_SERVICE | VEHICLE_REQUEST | 출장 차량 신청 | 출장 일정 기반 차량 예약 및 배차 요청 |
| INTRANET_SERVICE | EQUIPMENT_RENTAL | 비품 대여 | 노트북, 빔프로젝터, 케이블 등 비품 대여 |
| INTRANET_SERVICE | OFFICE_QNA | 사내 Q&A | 총무, 시설, 복지 관련 문의 접수 및 답변 |
## 4. 권한 및 보안 설계
### 4.1 역할 기반 접근 제어(RBAC)
| 역할 | 접근 범위 | 주요 권한 |
| --- | --- | --- |
| Admin | 전사 전체 워크스페이스 | 전체 통계 조회, 모든 게시글/신청 수정·삭제, 권한 부여, 워크스페이스 관리 |
| Support | 본인 담당 워크스페이스 | 신청 승인/반려, 배정 처리, 상태 변경, FAQ 관리, 답변 작성 |
| User | 본인이 접근 가능한 워크스페이스 | 신청 작성, 문의 작성, 본인 내역 조회, 댓글 작성, FAQ 조회 |
### 4.2 권한 검증 로직
- 모든 API 요청에서 BARON-SSO 토큰 기준 `user_id`, `tenant_id` 추출
- 전사 관리자 여부와 사용자 기본 정보는 BARON-SSO 기준 확인
- 워크스페이스 단위 접근 권한과 역할은 `user_workspace_access``can_read`, `can_write`, `can_manage`, `can_approve`, `page_scope` 기준 검증
- 요청 워크스페이스에 대한 권한이 없을 경우 `403 Forbidden` 반환
- S/W별 Q&A 게시글과 사내 지원 신청은 모두 작성자 본인, 권한 있는 Support, Admin 한정 조회
- S/W별 Q&A 워크스페이스는 사내 사용자(`INTERNAL`)와 외부 고객(`EXTERNAL`) 모두 접근 가능하도록 설계하고, 인트라넷형 서비스 워크스페이스는 사내 사용자 중심으로 제한 적용
## 5. 주요 기능 및 UI 설계
### 5.1 공통 워크스페이스 리스트
- 워크스페이스별, 상태별, 날짜별 필터 제공
- S/W별 Q&A와 인트라넷형 사내 지원 서비스를 하나의 공통 리스트 엔진으로 구성
- 사용자 진입 컨텍스트에 따라 해당 워크스페이스 화면만 우선 노출하는 구조 적용
- 문의, 요청, 처리중, 완료, 반려 등 상태값의 컬러 배지 구분을 통한 가시성 강화
### 5.2 S/W별 Q&A 기능
- 각 S/W 로그인 이후 해당 앱 전용 Q&A 게시판으로 직접 진입하는 앱 컨텍스트 기반 게시판 기능
- 제품별 문의 작성, 댓글 답변, 비밀글, 상태 변경 기능
- 질문 작성 시 관련 FAQ 추천 및 기존 문의 검색 기능
- 복잡한 문의에 대한 원격 지원 전환 및 지원 이력 관리 기능
- 앱별 공지, 신규 문의 현황, 담당자 답변 상태 표시 기능
### 5.3 인트라넷형 사내 지원 기능
- 물품신청: 품목, 수량, 사용 목적 입력 기반 신청 기능
- 도서 신청: 도서명, 저자, 출판사, 신청 사유 입력 기반 신청 기능
- 출장 차량 신청: 출장 일정, 목적지, 탑승 인원 기반 차량 신청 기능
- 비품 대여: 대여 품목, 사용 기간, 반납 예정일 입력 기반 대여 신청 기능
- 사내 Q&A: 총무, 시설, 복지, 기타 운영 문의 작성 및 답변 기능
### 5.4 승인, 배정 및 원격 처리
- 사내 지원 요청에 대한 담당자 승인 또는 반려 처리 기능
- 차량, 도서, 비품 등 실제 자산 배정 처리 기능
- 반려 사유, 처리 메모, 배정 결과의 이력 관리 기능
- 반납 완료 시 상태 자동 갱신 및 이력 보존 기능
- S/W별 Q&A 문의에 대한 원격 지원 일정 등록 및 처리 상태 관리 기능
### 5.5 FAQ, 공지 및 추천 기능
- 워크스페이스별 FAQ 및 공지사항 분리 관리 기능
- 질문 또는 신청 작성 시 관련 FAQ 및 기존 공지사항 추천 기능
- 자주 발생하는 요청 유형과 반복 문의의 사전 안내를 통한 중복 문의 감소 기능
- S/W별 앱 FAQ와 인트라넷형 서비스 안내 문서를 동일한 추천 구조로 제공
### 5.6 알림 및 이벤트 연동
- S/W별 Q&A 신규 문의 등록 시 담당자 대상 네이버웍스 알림 발송
- S/W별 Q&A 답변 등록 시 작성자 대상 네이버웍스 알림 발송
- 사내 지원 신청 등록 시 담당자 대상 네이버웍스 알림 발송
- 승인, 반려, 배정, 반납 처리 시 신청자 대상 네이버웍스 알림 발송
- 처리 지연 건 발생 시 담당자 리마인드 알림 발송
- 워크스페이스 유형별 알림 대상 라우팅 및 발송 이력 관리 체계 구성
- 네이버웍스 계정이 없는 사용자 또는 네이버웍스 발송 실패 건에 대한 SMS 대체 알림 발송 기능
- 알림 발송 채널, 실패 사유, 대체 발송 결과를 `notification_logs` 기준으로 추적 관리
## 6. 역할별 사용 시나리오
### 6.1 S/W 사용자(User) 시나리오
- 각 S/W 프로그램 로그인
- 사내 사용자 또는 외부 고객 자격으로 해당 앱 접근
- 해당 앱의 Q&A 화면으로 직접 진입
- 해당 앱 기준 Q&A 리스트 노출 및 기존 문의 확인
- 신규 문의 글 작성
- 담당자의 답변글 등록 시 네이버웍스 알림 수신 또는 SMS 대체 알림 수신
- 알림 확인 후 해당 앱 Q&A 화면에서 답변글 확인
```mermaid
flowchart LR
A[사내 사용자 또는 외부 고객 로그인] --> B[해당 S/W Q&A 진입]
B --> C[해당 앱 Q&A 리스트 확인]
C --> D[신규 문의 글 작성]
D --> E[담당자 답변 등록]
E --> F[네이버웍스 또는 SMS 알림 수신]
F --> G[답변글 확인]
```
### 6.2 S/W 담당자(Support) 시나리오
- 본인 담당 S/W 워크스페이스 접근
- 신규 문의 및 새글 표시 확인
- 문의 내용 검토 후 답변글 작성
- 상태값 변경 또는 원격 지원 전환 처리
- 작성자 대상 알림 발송
```mermaid
flowchart LR
A[담당 S/W 워크스페이스 접근] --> B[신규 문의 확인]
B --> C[문의 내용 검토]
C --> D[답변 또는 원격 지원 처리]
D --> E[상태값 변경]
E --> F[사용자 대상 알림 발송]
```
### 6.3 인트라넷 사용자(User) 시나리오
- 회사 인트라넷 접속 및 BARON-SSO 로그인
- 신청 서비스 선택
- 물품신청, 도서 신청, 출장 차량 신청, 비품 대여 또는 사내 Q&A 작성
- 처리 상태 및 승인 결과 확인
- 네이버웍스 알림 수신 후 상세 내역 확인
```mermaid
flowchart LR
A[인트라넷 접속 및 로그인] --> B[신청 서비스 선택]
B --> C[신청서 또는 문의 작성]
C --> D[담당자 처리 진행]
D --> E[승인 또는 반려 결과 확정]
E --> F[네이버웍스 알림 수신]
F --> G[상세 내역 확인]
```
### 6.4 인트라넷 담당자(Support) 시나리오
- 본인 담당 서비스 워크스페이스 목록 확인
- 신규 신청 또는 문의 접수 확인
- 승인, 반려, 배정, 답변 처리 수행
- 상태값 변경 및 처리 메모 등록
- 신청자 대상 알림 발송
```mermaid
flowchart LR
A[담당 서비스 워크스페이스 확인] --> B[신규 요청 접수 확인]
B --> C[승인 또는 반려 검토]
C --> D[배정 또는 답변 처리]
D --> E[상태값 변경 및 메모 등록]
E --> F[신청자 대상 알림 발송]
```
### 6.5 관리자(Admin) 시나리오
- 로컬 개발 환경에서 기능 개발 및 수정
- Git 저장소 업로드
- CI/CD 파이프라인을 통한 운영 서버 Docker 환경 배포
- S/W 앱 마스터, 서비스 유형, 워크스페이스 권한, 자산 마스터 관리
- 배포 결과 및 운영 상태 확인
```mermaid
flowchart LR
A[로컬 개발 및 수정] --> B[Git 저장소 업로드]
B --> C[CI/CD 파이프라인 실행]
C --> D[운영 서버 Docker 배포]
D --> E[앱/서비스/권한/자산 마스터 관리]
E --> F[운영 상태 확인]
```
## 7. 구현 로드맵
### 7.1 Phase 1. 핵심 인프라 구축
- [ ] Ubuntu 및 Docker 서버 환경 셋업
- [ ] BARON-SSO OAuth2 연동 및 사용자 매핑 로직 구현
- [ ] 공용 워크스페이스 기반 DB 스키마 생성 및 기초 API 개발
- [ ] S/W별 Q&A와 인트라넷형 서비스의 공통 인증 및 권한 구조 구현
### 7.2 Phase 2. S/W별 Q&A 및 공용 게시판 기능 개발
- [ ] S/W별 격리 게시판 및 통합 리스트 UI 구현
- [ ] 댓글, 첨부파일, 비밀글, FAQ 추천 기능 구현
- [ ] 원격 지원 전환 및 상태 관리 기능 구축
- [ ] 네이버웍스 알림, SMS 대체 알림 및 앱별 컨텍스트 진입 기능 구현
### 7.3 Phase 3. 인트라넷형 사내 지원 기능 개발
- [ ] 서비스 유형별 신청서 UI 및 통합 리스트 구현
- [ ] 승인/반려/배정 처리 기능 구현
- [ ] 자산 및 차량 일정 관리 기능 구축
- [ ] 첨부파일 및 댓글 시스템 구축
### 7.4 Phase 4. 고도화 및 운영 최적화
- [ ] FAQ 추천 및 중복 문의·중복 신청 사전 방지 기능 탑재
- [ ] 네이버웍스 알림, SMS 대체 발송, 처리 지연 리마인드 기능 고도화
- [ ] S/W별 Q&A 통계와 사내 지원 서비스 운영 인사이트 보고서 자동화
### 7.5 추후 개발 예정 기능
#### 7.5.1 공용 플랫폼 확장 기능
- 조직도 기반 결재선 자동 추천 기능: 신청 유형과 부서 기준으로 결재 대상 자동 추천 기능
- 자산 메타정보 동기화 기능: 사내 자산관리 시스템 또는 외부 마스터 정보와 비품, 차량, 도서 정보를 자동 동기화하는 기능
- 도서 및 비품 재고 예측 기능: 사용량 기반 부족 품목 예측 및 사전 구매 추천 기능
- 서비스별 맞춤 대시보드 기능: 부서별 처리량, 반려율, 평균 승인 시간 시각화 기능
- 앱 메타정보 동기화 기능: BARON-SSO 또는 외부 시스템에 등록된 S/W 정보를 공용 플랫폼과 자동으로 맞추는 기능
- S/W별 Q&A 딥링크 또는 임베드 연동 기능: 각 소프트웨어 내부에서 해당 앱의 Q&A 화면으로 바로 이동하거나 일부 화면을 직접 표시하는 기능
- 사내 포털 딥링크 또는 임베드 연동 기능: 그룹웨어 또는 사내 포털에서 해당 서비스 화면으로 바로 이동하거나 일부 화면을 직접 표시하는 기능
#### 7.5.2 적용 검토 기준
- 사내 자산관리, 그룹웨어, 조직도 API의 실제 연동 가능 범위 확인
- BARON-SSO 또는 외부 시스템의 앱 메타정보 동기화 가능 범위 확인
- 결재 정책과 운영 프로세스의 시스템 반영 가능 여부 확인
- 네이버웍스 알림 대상자 및 부서별 알림 정책 적용 가능 여부 확인
- 외부 고객 대상 SMS 발송 정책 및 개인정보 보관 기준 적용 가능 여부 확인
- 운영 복잡도 대비 활용 효과가 높은 기능 우선 적용
@@ -0,0 +1,497 @@
# 사내 지원 플랫폼 통합 아키텍처 설계서
## 1. 문서 목적
본 문서는 BARON-SSO 기반 공용 플랫폼으로 다음 두 서비스를 하나의 구조로 통합하는 방안을 설명함.
- 각 S/W 프로그램에서 진입하는 S/W별 Q&A 플랫폼
- 회사 인트라넷에서 진입하는 사내 지원 플랫폼
본 문서는 상세 테이블 설명보다 먼저 서비스의 전체 그림, 사용자 진입 방식, 인증 구조, 핵심 처리 계층, 외부 연동 방식을 이해할 수 있도록 정리함.
## 2. 한눈에 보는 서비스 구조
### 2.1 통합 대상 서비스
| 구분 | 설명 |
| --- | --- |
| S/W Q&A | 각 S/W 프로그램에서 로그인 후 해당 앱 전용 Q&A 게시판으로 연결되는 지원 서비스 |
| 사내 지원 | 인트라넷에서 진입하여 물품신청, 도서 신청, 출장 차량 신청, 비품 대여, 사내 Q&A를 처리하는 지원 서비스 |
### 2.2 핵심 설계 판단
- 사용자와 tenant의 원본 정보는 BARON-SSO에서 관리
- 플랫폼 내부에서는 사용자 마스터를 별도로 두지 않고 `user_id`, `tenant_id` 기반으로 권한만 관리
- 두 서비스는 각각 별도 시스템으로 만들지 않고 공통 `workspace` 기반 플랫폼으로 통합
- 게시글, 신청, 댓글, 첨부, FAQ, 알림은 공통 엔진으로 처리
- 승인, 자산, 차량, 원격지원은 서비스별 확장 기능으로 분리
- 승인 및 반려는 일반 사용자 화면이 아니라 담당자용 운영 페이지에서 처리
## 3. High-Level Architecture
### 3.1 아키텍처 관점
| 관점 | 설명 |
| --- | --- |
| 사용자 접점 | 사용자가 어디서 진입하는지 |
| 인증 계층 | BARON-SSO가 어디에서 인증을 담당하는지 |
| 핵심 서비스 | 어떤 애플리케이션 계층에서 업무를 처리하는지 |
| 인프라 및 외부 연동 | 데이터 저장과 알림, 외부 시스템 연동이 어디서 발생하는지 |
### 3.2 High-Level Architecture 다이어그램
```mermaid
flowchart LR
subgraph U[사용자 영역]
U1[인트라넷 사용자]
U2[사내 S/W 사용자]
U3[외부 고객]
end
subgraph E[사용자 접점]
E1[인트라넷 포털]
E2[각 S/W 프로그램]
end
subgraph A[인증 계층]
A1[BARON-SSO\nOAuth 2.0 / OIDC]
end
subgraph P[플랫폼 시스템]
subgraph T1[UI Tier]
F1[웹 클라이언트\nNext.js]
F2[운영 / 관리 페이지\n담당자 · 승인자 · 관리자]
end
subgraph T2[API Tier]
B1[지원 플랫폼 API\nFastAPI]
B2[권한 제어\nuser_id, tenant_id, workspace 기반]
B3[업무 처리 엔진\nQ&A / 신청 / 승인 / 자산 / 차량 / 원격지원]
B4[알림 처리\nNaver Works 우선, SMS 대체]
end
subgraph T3[Data Tier]
D1[PostgreSQL]
end
end
subgraph X[외부 연동]
X1[Naver Works API]
X2[SMS Gateway]
X3[자산 / 조직도 / 기타 사내 API]
end
U1 --> E1
U2 --> E2
U3 --> E2
E1 --> F1
E2 --> F1
F1 --> A1
A1 --> F1
F1 --> B1
F2 --> A1
A1 --> F2
F2 --> B1
B1 --> B2
B1 --> B3
B2 --> D1
B3 --> D1
B1 --> B4
B4 --> X1
B4 --> X2
B3 --> X3
```
### 3.3 전체 흐름 요약
1. 사용자는 인트라넷 포털 또는 각 S/W 프로그램에서 지원 플랫폼으로 진입함.
2. 웹 클라이언트는 BARON-SSO를 통해 인증을 수행하고 `user_id`, `tenant_id`를 확보함.
3. 플랫폼 API는 진입 경로의 `app_id` 또는 `service_type_id`를 내부 `workspace`로 매핑함.
4. 권한 제어 계층은 사용자별 읽기, 쓰기, 관리, 승인 범위를 확인함.
5. 일반 사용자는 사용자 화면에서 문의 또는 신청을 등록하고, 담당자는 운영 페이지에서 승인, 반려, 답변, 상태 변경을 처리함.
6. 데이터는 PostgreSQL에 저장하고 알림은 네이버웍스 우선, 실패 시 SMS로 대체 발송함.
7. 필요 시 자산 시스템, 조직도, 기타 사내 API와 연계함.
## 4. 서비스 구성
### 4.1 공통 플랫폼으로 통합하는 이유
두 서비스는 진입 채널과 세부 기능은 다르지만, 실제로는 다음 기능을 공통으로 사용함.
- 게시글 또는 신청서 작성
- 담당자 답변 및 처리 이력 관리
- 첨부파일 관리
- FAQ 및 공지 제공
- 권한별 화면 노출
- 상태 변경 및 알림 발송
따라서 서비스별로 별도 시스템을 만드는 대신 공통 플랫폼을 두고, 서비스별 차이는 `workspace`와 확장 테이블로 흡수하는 구조가 적절함.
### 4.2 S/W Q&A 서비스
S/W Q&A 서비스는 각 프로그램 사용자 또는 외부 고객이 해당 앱의 전용 게시판에 접속하여 문의를 등록하고 답변을 받는 구조임.
주요 기능은 다음과 같음.
- 앱별 전용 게시판 제공
- 문의 작성 및 담당자 답변
- 비밀글 처리
- 상태 변경 및 FAQ 추천
- 필요 시 원격지원 일정 등록
- 답변 등록 또는 상태 변경 시 알림 발송
### 4.3 사내 지원 서비스
사내 지원 서비스는 인트라넷에서 접근하는 업무 지원 포털 성격의 서비스임.
지원 범위는 다음과 같음.
- 물품신청
- 도서 신청
- 출장 차량 신청
- 비품 대여
- 사내 Q&A
공통 처리 흐름은 다음과 같음.
- 신청서 작성
- 승인 또는 반려
- 자산 배정 또는 차량 일정 등록
- 처리 결과 알림 발송
### 4.4 운영 및 관리 페이지
사내 지원 서비스에는 담당자가 승인 또는 반려를 처리하는 운영 페이지가 필요함. 이는 단순 상태 변경 화면이 아니라 권한과 이력 관리의 중심 화면 역할을 담당함.
운영 페이지의 필요 이유는 다음과 같음.
- 승인 대기 건을 한 번에 조회 가능
- 신청 상세 내용을 확인한 뒤 승인 또는 반려 처리 가능
- 반려 사유 입력 및 승인 이력 관리 가능
- 승인 이후 자산 배정, 차량 일정 등록, 후속 알림 발송까지 연결 가능
- `can_approve`, `can_manage`, `page_scope` 권한과 직접 연결 가능
운영 페이지는 별도 시스템으로 분리하기보다 동일 플랫폼 내부의 권한 기반 메뉴로 구성하는 방식이 적절함.
## 5. 핵심 구성요소 상세
### 5.1 사용자 접점
| 접점 | 설명 |
| --- | --- |
| 인트라넷 포털 | 사내 지원 서비스 진입점 |
| 각 S/W 프로그램 | S/W별 Q&A 서비스 진입점 |
| 운영 / 관리 페이지 | 담당자, 승인자, 관리자가 사용하는 내부 운영 화면 |
인트라넷에서는 서비스 유형 기준으로 진입하고, S/W 프로그램에서는 앱 기준으로 진입함. 운영 담당자는 별도 운영 메뉴를 통해 진입하지만, 이 역시 내부적으로는 동일한 `workspace`와 권한 체계를 사용함.
### 5.2 인증 및 권한 계층
BARON-SSO는 사용자 인증과 tenant 식별의 원본 시스템 역할을 담당함. 플랫폼은 BARON-SSO로부터 받은 `user_id`, `tenant_id`를 기준으로 내부 권한만 제어함.
플랫폼 내부 권한 항목은 다음과 같음.
- `can_read`
- `can_write`
- `can_manage`
- `can_approve`
- `page_scope`
이 구조를 사용하면 사용자 기본 정보는 외부에서 일관되게 유지하고, 플랫폼 내부에서는 읽기, 쓰기, 승인, 관리 범위만 유연하게 제어 가능함.
특히 승인 또는 반려 처리는 `can_approve` 권한이 있는 담당자만 수행하도록 제한하고, 운영 화면 접근 범위는 `page_scope`로 분리하는 방식이 적절함.
### 5.3 핵심 서비스 계층
핵심 서비스 계층은 FastAPI 기반 API 서버로 구성하며, 다음 기능을 공통 처리함.
| 기능 영역 | 설명 |
| --- | --- |
| Workspace Engine | 앱 또는 인트라넷 서비스를 내부 작업 단위로 매핑 |
| Ticket Engine | Q&A와 신청서를 공통 구조로 저장 및 처리 |
| Comment Engine | 답변, 처리 메모, 협업 이력 관리 |
| Attachment Engine | 첨부파일 저장 및 조회 관리 |
| FAQ Engine | 워크스페이스별 FAQ와 공지성 정보 제공 |
| Approval Extension | 신청 승인 및 반려 처리 |
| Asset Extension | 물품, 비품, 도서, 차량 자산 관리 |
| Remote Support Extension | S/W 문의의 원격지원 일정 및 처리 관리 |
이 중 Approval Extension은 일반 사용자 화면보다 운영 페이지와 더 강하게 연결됨. 승인 담당자는 운영 페이지에서 대기 건 조회, 승인 또는 반려 처리, 반려 사유 작성, 후속 조치 등록을 수행함.
### 5.4 데이터 및 외부 연동 계층
PostgreSQL은 플랫폼의 공통 데이터 저장소 역할을 담당함. 외부 연동은 알림과 운영 정보 보강 목적에 집중함.
| 연동 대상 | 목적 |
| --- | --- |
| Naver Works API | 기본 알림 채널 |
| SMS Gateway | 네이버웍스 실패 또는 미보유 사용자 대체 알림 |
| 자산/조직도/기타 사내 API | 자산 정보 조회, 조직 기반 처리, 추가 업무 연계 |
알림은 네이버웍스를 우선 사용하고, 발송 실패 또는 계정 미보유 시 SMS로 대체하는 정책을 적용함.
## 6. DB 설계 방향
### 6.1 설계 원칙
- 사용자 마스터는 BARON-SSO에서 관리함.
- 로컬 DB는 권한, 워크스페이스, 업무 데이터, 알림 보조 정보만 관리함.
- Q&A와 사내 지원 요청은 분리 저장하지 않고 공통 티켓 구조로 통합함.
- 서비스별 차이는 확장 테이블로 분리하여 향후 신규 앱과 신규 사내 서비스가 추가되어도 구조 변경을 최소화함.
- 운영 페이지는 별도 사용자 테이블 없이 기존 권한 테이블과 승인 이력 테이블을 활용하여 구성함.
### 6.2 데이터 영역 구분
| 데이터 영역 | 주요 테이블 | 설명 |
| --- | --- | --- |
| 서비스 마스터 | `software_apps`, `service_types`, `workspaces` | 진입 경로를 내부 서비스 단위로 매핑 |
| 권한 관리 | `user_workspace_access` | 사용자별 읽기, 쓰기, 관리, 승인 범위 제어 |
| 알림 보조 정보 | `user_notification_profiles` | 네이버웍스 키, 전화번호, SMS 수신 여부 관리 |
| 공통 업무 데이터 | `support_tickets`, `ticket_comments`, `attachments`, `faqs` | Q&A와 신청 데이터를 공통 구조로 관리 |
| 코드 관리 | `support_status_codes`, `support_category_codes`, `remote_support_status_codes` | 상태 및 분류 표준화 |
| 사내 지원 확장 | `request_approvals`, `assets`, `asset_allocations`, `vehicle_schedules` | 승인, 자산, 차량 업무 처리 |
| S/W 지원 확장 | `remote_support` | 원격지원 일정 및 처리 이력 관리 |
| 운영 이력 | `notification_logs` | 알림 발송 및 실패 이력 추적 |
### 6.3 핵심 엔터티 설명
#### 6.3.1 workspace
`workspace`는 이 설계의 중심 엔터티임. 외부에서는 S/W 앱 또는 인트라넷 서비스로 보이지만, 내부에서는 모두 `workspace`로 수렴함. 이 구조를 사용하면 신규 앱 또는 신규 사내 서비스가 생겨도 공통 기능을 재사용 가능함.
#### 6.3.2 support_tickets
Q&A 게시글과 각종 신청서를 별도 본문 테이블로 나누지 않고 `support_tickets`로 통합 관리함. 대신 `ticket_type`, `workspace_id`, `category_code`, `status_code`로 의미를 구분함.
#### 6.3.3 user_workspace_access
플랫폼은 사용자 상세 프로필을 저장하지 않지만, 어떤 사용자가 어떤 워크스페이스에서 무엇을 할 수 있는지는 반드시 관리해야 함. 이 역할을 `user_workspace_access`가 담당함.
운영 페이지 관점에서는 이 테이블이 특히 중요함. 승인 담당자 여부, 운영 메뉴 접근 가능 여부, 특정 워크스페이스에 대한 승인 가능 범위가 모두 이 테이블의 권한 컬럼으로 제어되기 때문임.
#### 6.3.4 request_approvals
`request_approvals`는 승인 또는 반려가 실제로 수행된 결과를 남기는 이력 테이블임. 승인 담당자 정보, 처리 결과, 반려 사유, 승인 시각을 저장하므로 운영 페이지의 감사 추적과 처리 내역 조회에 직접 사용됨.
## 7. DB 스키마 초안
```sql
-- 1. 소프트웨어 정보
CREATE TABLE software_apps (
id SERIAL PRIMARY KEY,
app_code VARCHAR(50) UNIQUE NOT NULL,
app_name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2. 사내 서비스 유형 정보
CREATE TABLE service_types (
id SERIAL PRIMARY KEY,
service_code VARCHAR(50) UNIQUE NOT NULL,
service_name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 3. 공통 워크스페이스 마스터
CREATE TABLE workspaces (
id SERIAL PRIMARY KEY,
workspace_type VARCHAR(20) NOT NULL,
software_app_id INTEGER REFERENCES software_apps(id),
service_type_id INTEGER REFERENCES service_types(id),
workspace_code VARCHAR(50) UNIQUE NOT NULL,
workspace_name VARCHAR(100) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHECK (
(workspace_type = 'SOFTWARE_APP' AND software_app_id IS NOT NULL AND service_type_id IS NULL)
OR
(workspace_type = 'INTRANET_SERVICE' AND service_type_id IS NOT NULL AND software_app_id IS NULL)
)
);
-- 4. 사용자별 워크스페이스 접근 권한 및 역할
CREATE TABLE user_workspace_access (
id SERIAL PRIMARY KEY,
user_id VARCHAR(100) NOT NULL,
tenant_id VARCHAR(100) NOT NULL,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
workspace_role VARCHAR(20) NOT NULL DEFAULT 'USER',
can_read BOOLEAN DEFAULT TRUE,
can_write BOOLEAN DEFAULT FALSE,
can_manage BOOLEAN DEFAULT FALSE,
can_approve BOOLEAN DEFAULT FALSE,
page_scope VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, tenant_id, workspace_id)
);
-- 5. 사용자 알림 보조 정보
CREATE TABLE user_notification_profiles (
id SERIAL PRIMARY KEY,
user_id VARCHAR(100) NOT NULL,
tenant_id VARCHAR(100) NOT NULL,
user_type VARCHAR(20) NOT NULL DEFAULT 'INTERNAL',
phone_number VARCHAR(30),
naverworks_user_key VARCHAR(100),
sms_opt_in BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, tenant_id)
);
-- 6. 공통 상태 코드
CREATE TABLE support_status_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 7. 공통 카테고리 코드
CREATE TABLE support_category_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 8. 공통 게시글/신청 본문
CREATE TABLE support_tickets (
id SERIAL PRIMARY KEY,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
requester_id VARCHAR(100) NOT NULL,
requester_tenant_id VARCHAR(100) NOT NULL,
ticket_type VARCHAR(20) NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT,
category_code VARCHAR(20) REFERENCES support_category_codes(code),
status_code VARCHAR(20) NOT NULL DEFAULT 'OPEN' REFERENCES support_status_codes(code),
is_secret BOOLEAN DEFAULT FALSE,
requested_start_at TIMESTAMP,
requested_end_at TIMESTAMP,
priority VARCHAR(20) DEFAULT 'NORMAL',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 9. 댓글 및 처리 메모
CREATE TABLE ticket_comments (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
author_id VARCHAR(100) NOT NULL,
author_tenant_id VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 10. 첨부파일 통합 관리
CREATE TABLE attachments (
id SERIAL PRIMARY KEY,
parent_type VARCHAR(20) NOT NULL,
parent_id INTEGER NOT NULL,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 11. 사내 지원용 승인 이력
CREATE TABLE request_approvals (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
approver_id VARCHAR(100) NOT NULL,
approver_tenant_id VARCHAR(100) NOT NULL,
approval_status VARCHAR(20) NOT NULL,
comment TEXT,
approved_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 12. 자산/비품 마스터
CREATE TABLE assets (
id SERIAL PRIMARY KEY,
asset_code VARCHAR(50) UNIQUE NOT NULL,
asset_name VARCHAR(100) NOT NULL,
asset_type VARCHAR(30) NOT NULL,
quantity INTEGER DEFAULT 1,
is_active BOOLEAN DEFAULT TRUE,
location VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 13. 자산 배정 및 대여 이력
CREATE TABLE asset_allocations (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
asset_id INTEGER NOT NULL REFERENCES assets(id),
assignee_id VARCHAR(100),
assignee_tenant_id VARCHAR(100),
allocation_status VARCHAR(20) NOT NULL,
loaned_at TIMESTAMP,
due_at TIMESTAMP,
returned_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 14. 차량 운행 일정
CREATE TABLE vehicle_schedules (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
asset_id INTEGER NOT NULL REFERENCES assets(id),
departure_at TIMESTAMP NOT NULL,
arrival_at TIMESTAMP,
destination VARCHAR(255),
driver_name VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 15. S/W Q&A용 원격 지원 상태 코드
CREATE TABLE remote_support_status_codes (
code VARCHAR(20) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL
);
-- 16. S/W Q&A용 원격 지원 로그
CREATE TABLE remote_support (
id SERIAL PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES support_tickets(id),
status_code VARCHAR(20) REFERENCES remote_support_status_codes(code),
support_engineer_id VARCHAR(100),
support_engineer_tenant_id VARCHAR(100),
scheduled_time TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 17. 공통 FAQ
CREATE TABLE faqs (
id SERIAL PRIMARY KEY,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 18. 알림 발송 이력
CREATE TABLE notification_logs (
id SERIAL PRIMARY KEY,
ticket_id INTEGER REFERENCES support_tickets(id),
recipient_id VARCHAR(100) NOT NULL,
recipient_tenant_id VARCHAR(100) NOT NULL,
channel VARCHAR(20) NOT NULL,
target_address VARCHAR(100),
delivery_status VARCHAR(20) NOT NULL,
fallback_channel VARCHAR(20),
error_message TEXT,
sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
## 8. 정리
이 설계의 핵심은 S/W별 Q&A와 인트라넷 사내 지원을 서로 다른 시스템으로 분리하지 않고, BARON-SSO와 `workspace` 중심 공통 플랫폼으로 통합하는 데 있음. 사용자 정보는 BARON-SSO를 원본으로 유지하고, 플랫폼은 권한과 업무 처리에 집중함. 그 결과 서비스 확장성과 운영 일관성을 동시에 확보 가능함.
다음 단계에서는 상태 코드 표준값, `page_scope` 체계, 주요 API 목록, 화면 구성도를 추가하면 구현 준비 수준의 설계 문서로 확장 가능함.
@@ -0,0 +1,698 @@
# 사내 지원 플랫폼 통합 아키텍처 설계서
## 1. 문서 목적
본 문서는 BARON-SSO 기반 공용 플랫폼으로 다음 두 서비스를 하나의 구조로 통합하는 방안을 설명함.
- 각 S/W 프로그램에서 진입하는 S/W별 Q&A 플랫폼
- 회사 인트라넷에서 진입하는 사내 지원 플랫폼
이번 설계의 핵심은 기존 ABC User Feedback 솔루션을 그대로 폐기하지 않고, 원본 게시글 저장소와 채널 관리 도구로 활용하면서 우리 시스템이 권한 제어, 승인 워크플로우, 동적 폼, 운영 대시보드를 담당하는 구조로 고도화하는 데 있음.
## 2. 한눈에 보는 통합 방향
### 2.1 통합 대상 서비스
| 구분 | 설명 |
| --- | --- |
| S/W Q&A | 각 S/W 프로그램에서 로그인 후 해당 앱 전용 Q&A 채널로 연결되는 지원 서비스 |
| 사내 지원 | 인트라넷에서 진입하여 물품신청, 도서 신청, 출장 차량 신청, 비품 대여, 사내 Q&A를 처리하는 지원 서비스 |
### 2.2 핵심 설계 판단
- 사용자 인증과 `tenant_id` 식별의 원본은 BARON-SSO가 담당함.
- ABC User Feedback는 게시글 원본 저장소이자 관리자 기반 채널/필드 관리 도구로 사용함.
- 우리 시스템은 FastAPI와 자체 DB를 통해 권한 분기, 상태 제어, 승인 이력, 운영 화면을 담당함.
- 외부 진입 경로의 `app_id`, `service_type_id`는 내부 `workspace`로 매핑함.
- 일반 사용자는 Next.js 동적 폼과 조회 화면을 사용하고, 담당자는 운영 페이지에서 승인/반려/후속 조치를 처리함.
- Q&A와 신청은 공통 티켓 모델로 다루되, 실제 원본 본문은 ABC에 저장하고 우리 DB에는 제어용 메타데이터와 매핑 정보를 저장함.
### 2.3 역할 분리 요약
| 구성요소 | 주 역할 | 저장 데이터 |
| --- | --- | --- |
| BARON-SSO | 사용자 인증, `user_id`, `tenant_id` 발급 | 사용자/조직 원본 정보 |
| ABC User Feedback | 게시글 저장, 댓글/첨부 관리, 채널/필드 구성, 기본 목록/상세 UI | 피드백 원본 데이터, 채널 설정, 필드 값 |
| 우리 시스템 | 권한 필터링, 승인 워크플로우, 상태 제어, 운영 페이지, 동적 폼, 외부 연동 오케스트레이션 | 권한, 워크스페이스, 매핑, 승인, 자산, 차량, 알림, 운영 이력 |
## 3. High-Level Architecture
### 3.1 아키텍처 관점
| 관점 | 설명 |
| --- | --- |
| 사용자 접점 | 사용자가 어디서 진입하는지 |
| 인증 계층 | BARON-SSO가 어디에서 인증을 담당하는지 |
| 데이터 저장 계층 | ABC와 자체 DB가 어떤 데이터를 나눠 저장하는지 |
| 제어 계층 | 권한, 승인, 운영 로직이 어디에서 처리되는지 |
| 외부 연동 | 알림, 자산, 조직도, 기타 사내 API가 어디에서 연결되는지 |
### 3.2 High-Level Architecture 다이어그램
아래 다이어그램은 처리 행위를 화살표 라벨로 표시하고, 결과가 쌓이거나 보여지는 지점은 사각형 박스로 구분한 구조임.
```mermaid
flowchart LR
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.5px,color:#0f172a;
classDef control fill:#f7f7f7,stroke:#334155,stroke-width:1.2px,color:#111827;
classDef external fill:#fff7ed,stroke:#c2410c,stroke-width:1.2px,color:#111827;
subgraph U[사용자 영역]
U1[인트라넷 사용자]
U2[S/W 사용자]
U3[운영 담당자]
end
subgraph E[진입 채널]
E1[인트라넷 포털]
E2[각 S/W 프로그램]
E3[운영 메뉴]
end
SSO[BARON-SSO\nOAuth 2.0 / OIDC]:::external
subgraph UI[화면 계층]
N1[Next.js 사용자 화면\n동적 폼 / 조회 / 상세]:::result
N2[Next.js 운영 페이지\n승인 / 반려 / 후속 조치]:::result
end
subgraph CTL[우리 시스템\nFastAPI + 자체 DB]
F1[권한 분기 엔진\nuser_id + tenant_id + workspace]:::control
F2[업무 프로세스 엔진\n상태 제어 / 승인 워크플로우 / 후속 조치]:::control
F3[브릿지 API\nABC API 연동 / 매핑 기록]:::control
D1[자체 제어 DB\n권한 / 매핑 / 승인 / 자산 / 차량 / 알림]:::result
end
subgraph ABC[ABC User Feedback]
A1[채널 / 필드 관리자 UI]:::result
A2[Feedback API\nPOST /feedbacks 등]:::control
A3[원본 데이터 저장소\n제목 / 본문 / 댓글 / 첨부 / 필드값]:::result
A4[기본 목록 / 상세 UI]:::result
end
subgraph X[외부 연동]
X1[Naver Works API]:::external
X2[SMS Gateway]:::external
X3[자산 / 조직도 / 기타 사내 API]:::external
end
U1 -->|인트라넷 서비스 진입| E1
U2 -->|앱 내부 지원 메뉴 진입| E2
U3 -->|운영 메뉴 진입| E3
E1 -->|사용자 화면 호출| N1
E2 -->|사용자 화면 호출| N1
E3 -->|운영 페이지 호출| N2
N1 -->|SSO 로그인 요청| SSO
N2 -->|운영자 인증 요청| SSO
SSO -->|user_id, tenant_id, 토큰 반환| N1
SSO -->|운영 권한 토큰 반환| N2
N1 -->|폼/목록 조회 요청| F1
N1 -->|신청/문의 등록 요청| F3
N2 -->|승인 대기/처리 요청| F2
F1 -->|workspace 매핑 및 권한 판정| D1
F2 -->|승인 이력 / 상태 저장| D1
F3 -->|매핑 정보 조회 및 기록| D1
F3 -->|채널/필드 조회| A1
F3 -->|원본 게시글 생성/조회| A2
A2 -->|피드백 원본 저장| A3
A2 -->|기본 목록/상세 제공| A4
F2 -->|알림 발송 요청| X1
F2 -->|실패 시 대체 발송| X2
F2 -->|자산/차량 후속 처리| X3
```
### 3.3 전체 처리 흐름 요약
1. 사용자는 인트라넷 포털 또는 S/W 프로그램에서 Next.js 사용자 화면으로 진입함.
2. 사용자 화면은 BARON-SSO를 통해 인증하고 `user_id`, `tenant_id`를 확보함.
3. 우리 시스템의 권한 분기 엔진은 진입 경로를 `workspace`로 매핑하고 조회/등록 가능 범위를 판단함.
4. 사용자가 문의 또는 신청서를 등록하면 브릿지 API가 ABC Feedback API로 원본 데이터를 저장함.
5. 동시에 우리 DB에는 `workspace`, 상태, 요청 유형, ABC 피드백 ID, 기존 시스템 ID, 승인 필요 여부 등 제어 정보를 저장함.
6. 담당자는 운영 페이지에서 승인 대기 건을 조회하고 `request_approvals` 기반으로 승인/반려/후속 조치를 처리함.
7. 승인 결과와 상태 변경은 우리 DB에 기록되고, 필요 시 Naver Works 또는 SMS, 자산/차량 API 연동이 수행됨.
## 4. 서비스 책임 분리
### 4.1 ABC User Feedback의 역할
ABC User Feedback는 기능 저장소이자 데이터 저장소로 사용함. 즉, 사용자에게 보이는 게시글 원본과 채널 구조는 ABC가 책임지고, 우리 시스템은 이를 직접 대체하지 않음.
ABC가 담당하는 범위는 다음과 같음.
- 게시글 원본 데이터 저장
- 제목, 본문, 댓글, 첨부파일 저장
- 채널 생성 및 채널별 필드 구성
- 신청서 항목을 표현하는 커스텀 필드 저장
- 시스템 API를 통한 피드백 생성 및 조회
- 기본 목록/상세 UI 제공
즉, ABC는 "무엇이 기록되었는가"를 보존하는 원본 시스템임.
### 4.2 우리 시스템의 역할
우리 시스템은 FastAPI, Next.js, 자체 DB를 사용하여 ABC 위에 제어 계층을 추가함.
우리 시스템이 담당하는 범위는 다음과 같음.
- `tenant_id``workspace` 기준 권한 분기
- 사용자별 조회 가능 데이터 필터링
- 신청서 상태 제어: 대기, 승인, 반려, 처리 완료
- `request_approvals` 기반 승인 워크플로우 처리
- 자산 배정, 차량 배차, 원격지원 등 확장 프로세스 처리
- 운영 대시보드 제공
- Next.js 동적 폼 렌더링 및 유효성 검사
- 기존 데이터 마이그레이션 시 ABC ID와 기존 글 ID 매핑 관리
즉, 우리 시스템은 "누가 무엇을 볼 수 있고, 어떤 절차로 처리되는가"를 책임지는 지능형 제어 엔진임.
### 4.3 화면 구성 원칙
| 화면 | 주 시스템 | 설명 |
| --- | --- | --- |
| 일반 사용자 입력 화면 | 우리 시스템 | 진입 경로별 동적 폼, 유효성 검사, 등록 프로세스 제어 |
| 일반 사용자 목록/상세 | 혼합 | 우리 시스템의 권한 필터링 결과와 ABC 원본 데이터를 조합하여 노출 |
| 운영 페이지 | 우리 시스템 | 승인/반려/후속 조치 전용 대시보드 |
| 채널/필드 관리자 화면 | ABC User Feedback | 채널 생성, 필드 구성, 스키마 관리 |
## 5. DB 설계 방향
### 5.1 설계 원칙
- 사용자 마스터와 조직 정보는 BARON-SSO에서 관리함.
- 게시글 원본, 댓글, 첨부, 채널 필드 값은 ABC에 저장함.
- 우리 DB는 제어용 메타데이터와 운영용 확장 데이터만 저장함.
- `support_tickets`는 내부 티켓 식별자이자 ABC 피드백과 연결되는 제어 엔트리 역할을 수행함.
- 마이그레이션과 운영 연계에 필요한 식별자 매핑은 별도 매핑 테이블로 관리함.
- 승인, 자산, 차량, 원격지원은 ABC 원본과 느슨하게 연결된 확장 테이블로 관리함.
### 5.2 데이터 저장 책임 분리
| 데이터 범주 | 저장 위치 | 설명 |
| --- | --- | --- |
| 사용자 인증 정보 | BARON-SSO | `user_id`, `tenant_id`, 토큰, 조직 기준 원본 |
| 게시글 원본 | ABC User Feedback | 제목, 본문, 댓글, 첨부, 사용자 입력 필드 값 |
| 권한/워크스페이스 | 우리 DB | `workspace`, 역할, 읽기/쓰기/승인/관리 권한 |
| 제어용 티켓 메타데이터 | 우리 DB | 내부 티켓 ID, 상태, ABC 피드백 ID, 승인 필요 여부 |
| 업무 확장 데이터 | 우리 DB | 승인, 자산, 차량, 원격지원, 알림 로그 |
| 마이그레이션 매핑 | 우리 DB | 기존 시스템 ID와 ABC 피드백 ID, 내부 티켓 ID 연결 |
### 5.3 핵심 관계 구조
```mermaid
flowchart TD
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.2px,color:#0f172a;
classDef control fill:#f7f7f7,stroke:#334155,stroke-width:1.2px,color:#111827;
W[workspaces]:::result -->|1:N 서비스 범위 정의| UWA[user_workspace_access]:::result
W -->|1:N 내부 티켓 소속| ST[support_tickets]:::result
ST -->|1:1 또는 1:N ABC 원본 연결| AFM[abc_feedback_mappings]:::result
AFM -->|ABC feedback_id 참조 기록| ABCREF[ABC feedback]:::control
ST -->|1:N 승인 이력| RA[request_approvals]:::result
ST -->|1:N 자산 배정| AA[asset_allocations]:::result
ST -->|1:N 차량 일정| VS[vehicle_schedules]:::result
ST -->|1:N 원격지원 이력| RS[remote_support]:::result
ST -->|1:N 알림 이력| NL[notification_logs]:::result
ST -->|1:N 마이그레이션 추적| MM[migration_mappings]:::result
```
## 6. DB 스키마 상세화
### 6.1 자체 DB 데이터 영역
| 데이터 영역 | 주요 테이블 | 설명 |
| --- | --- | --- |
| 서비스 마스터 | `software_apps`, `service_types`, `workspaces` | 진입 채널을 내부 `workspace`로 매핑 |
| 권한 관리 | `support_users`, `support_roles`, `support_role_assignments`, `user_workspace_access` | 내부 사용자·역할 할당과 workspace 유효 권한 제어 |
| 폼/채널 연동 | `workspace_channel_mappings`, `workspace_field_mappings` | `workspace`와 ABC 채널/필드 연결 |
| 공통 티켓 메타데이터 | `support_tickets` | 내부 상태, 요청자, 티켓 유형, 우선순위 관리 |
| ABC 연동 매핑 | `abc_feedback_mappings` | 내부 티켓과 ABC `feedback_id` 연결 |
| 마이그레이션 추적 | `migration_mappings`, `migration_batches` | 기존 글 ID, 기존 첨부 ID, ABC ID 적재 이력 관리 |
| 승인/운영 | `request_approvals`, `notification_logs` | 승인 및 알림 처리 이력 |
| 업무 확장 | `assets`, `asset_allocations`, `vehicle_schedules`, `remote_support` | 자산, 차량, 원격지원 업무 관리 |
### 6.2 핵심 상세 테이블 제안
아래 스키마는 ABC와 우리 DB의 역할 분리를 반영한 초안임.
```sql
CREATE TABLE software_apps (
id BIGINT NOT NULL AUTO_INCREMENT,
app_code VARCHAR(50) NOT NULL,
app_name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_software_apps_app_code (app_code),
UNIQUE KEY uq_software_apps_app_name (app_name)
);
CREATE TABLE service_types (
id BIGINT NOT NULL AUTO_INCREMENT,
service_code VARCHAR(50) NOT NULL,
service_name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_service_types_service_code (service_code),
UNIQUE KEY uq_service_types_service_name (service_name)
);
CREATE TABLE workspaces (
id BIGINT NOT NULL AUTO_INCREMENT,
workspace_type VARCHAR(20) NOT NULL,
software_app_id BIGINT NULL,
service_type_id BIGINT NULL,
workspace_code VARCHAR(50) NOT NULL,
workspace_name VARCHAR(100) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_workspaces_workspace_code (workspace_code),
CONSTRAINT fk_workspaces_software_app
FOREIGN KEY (software_app_id) REFERENCES software_apps(id),
CONSTRAINT fk_workspaces_service_type
FOREIGN KEY (service_type_id) REFERENCES service_types(id),
CONSTRAINT chk_workspaces_scope
CHECK (
(workspace_type = 'SOFTWARE_APP' AND software_app_id IS NOT NULL AND service_type_id IS NULL)
OR
(workspace_type = 'INTRANET_SERVICE' AND service_type_id IS NOT NULL AND software_app_id IS NULL)
)
);
CREATE TABLE user_workspace_access (
id BIGINT NOT NULL AUTO_INCREMENT,
user_id VARCHAR(100) NOT NULL,
tenant_id VARCHAR(100) NOT NULL,
workspace_id BIGINT NOT NULL,
workspace_role VARCHAR(30) NOT NULL DEFAULT 'USER',
can_read BOOLEAN NOT NULL DEFAULT TRUE,
can_write BOOLEAN NOT NULL DEFAULT FALSE,
can_manage BOOLEAN NOT NULL DEFAULT FALSE,
can_approve BOOLEAN NOT NULL DEFAULT FALSE,
page_scope VARCHAR(50),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_user_workspace_access_user_tenant_workspace (user_id, tenant_id, workspace_id),
CONSTRAINT fk_user_workspace_access_workspace
FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
);
CREATE TABLE workspace_channel_mappings (
id BIGINT NOT NULL AUTO_INCREMENT,
workspace_id BIGINT NOT NULL,
abc_channel_id VARCHAR(100) NOT NULL,
abc_channel_key VARCHAR(100),
form_template_version VARCHAR(30),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_workspace_channel_mappings_workspace_channel (workspace_id, abc_channel_id),
CONSTRAINT fk_workspace_channel_mappings_workspace
FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
);
CREATE TABLE workspace_field_mappings (
id BIGINT NOT NULL AUTO_INCREMENT,
workspace_id BIGINT NOT NULL,
abc_channel_id VARCHAR(100) NOT NULL,
abc_field_key VARCHAR(100) NOT NULL,
local_field_code VARCHAR(100) NOT NULL,
field_label VARCHAR(100) NOT NULL,
field_type VARCHAR(30) NOT NULL,
is_required BOOLEAN NOT NULL DEFAULT FALSE,
sort_order INT NOT NULL DEFAULT 0,
validation_rule JSON,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_workspace_field_mappings_workspace_channel_field (workspace_id, abc_channel_id, abc_field_key),
CONSTRAINT fk_workspace_field_mappings_workspace
FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
);
CREATE TABLE support_status_codes (
code VARCHAR(20) NOT NULL,
name VARCHAR(50) NOT NULL,
sort_order INT NOT NULL,
PRIMARY KEY (code)
);
CREATE TABLE support_category_codes (
code VARCHAR(20) NOT NULL,
name VARCHAR(50) NOT NULL,
sort_order INT NOT NULL,
PRIMARY KEY (code)
);
CREATE TABLE support_tickets (
id BIGINT NOT NULL AUTO_INCREMENT,
workspace_id BIGINT NOT NULL,
requester_id VARCHAR(100) NOT NULL,
requester_tenant_id VARCHAR(100) NOT NULL,
ticket_type VARCHAR(30) NOT NULL,
source_system VARCHAR(30) NOT NULL DEFAULT 'ABC',
title VARCHAR(255) NOT NULL,
category_code VARCHAR(20),
status_code VARCHAR(20) NOT NULL,
is_secret BOOLEAN NOT NULL DEFAULT FALSE,
requires_approval BOOLEAN NOT NULL DEFAULT FALSE,
current_assignee_id VARCHAR(100),
current_assignee_tenant_id VARCHAR(100),
requested_start_at TIMESTAMP NULL,
requested_end_at TIMESTAMP NULL,
priority VARCHAR(20) NOT NULL DEFAULT 'NORMAL',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT fk_support_tickets_workspace
FOREIGN KEY (workspace_id) REFERENCES workspaces(id),
CONSTRAINT fk_support_tickets_category_code
FOREIGN KEY (category_code) REFERENCES support_category_codes(code),
CONSTRAINT fk_support_tickets_status_code
FOREIGN KEY (status_code) REFERENCES support_status_codes(code)
);
CREATE TABLE abc_feedback_mappings (
id BIGINT NOT NULL AUTO_INCREMENT,
ticket_id BIGINT NOT NULL,
workspace_id BIGINT NOT NULL,
abc_channel_id VARCHAR(100) NOT NULL,
abc_feedback_id VARCHAR(100) NOT NULL,
abc_feedback_url TEXT,
sync_status VARCHAR(20) NOT NULL DEFAULT 'SYNCED',
last_synced_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_abc_feedback_mappings_ticket_id (ticket_id),
UNIQUE KEY uq_abc_feedback_mappings_feedback_id (abc_feedback_id),
CONSTRAINT fk_abc_feedback_mappings_ticket
FOREIGN KEY (ticket_id) REFERENCES support_tickets(id),
CONSTRAINT fk_abc_feedback_mappings_workspace
FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
);
CREATE TABLE migration_batches (
id BIGINT NOT NULL AUTO_INCREMENT,
batch_name VARCHAR(100) NOT NULL,
source_system VARCHAR(50) NOT NULL,
started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
status VARCHAR(20) NOT NULL DEFAULT 'RUNNING',
executed_by VARCHAR(100),
notes TEXT,
PRIMARY KEY (id)
);
CREATE TABLE migration_mappings (
id BIGINT NOT NULL AUTO_INCREMENT,
batch_id BIGINT NOT NULL,
source_system VARCHAR(50) NOT NULL,
source_entity_type VARCHAR(30) NOT NULL,
source_entity_id VARCHAR(100) NOT NULL,
source_parent_id VARCHAR(100),
workspace_id BIGINT NOT NULL,
ticket_id BIGINT NULL,
abc_feedback_id VARCHAR(100),
migration_status VARCHAR(20) NOT NULL,
error_message TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_migration_mappings_source_entity (source_system, source_entity_type, source_entity_id),
CONSTRAINT fk_migration_mappings_batch
FOREIGN KEY (batch_id) REFERENCES migration_batches(id),
CONSTRAINT fk_migration_mappings_workspace
FOREIGN KEY (workspace_id) REFERENCES workspaces(id),
CONSTRAINT fk_migration_mappings_ticket
FOREIGN KEY (ticket_id) REFERENCES support_tickets(id)
);
CREATE TABLE request_approvals (
id BIGINT NOT NULL AUTO_INCREMENT,
ticket_id BIGINT NOT NULL,
approver_id VARCHAR(100) NOT NULL,
approver_tenant_id VARCHAR(100) NOT NULL,
approval_status VARCHAR(20) NOT NULL,
comment TEXT,
approved_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT fk_request_approvals_ticket
FOREIGN KEY (ticket_id) REFERENCES support_tickets(id)
);
CREATE TABLE assets (
id BIGINT NOT NULL AUTO_INCREMENT,
asset_code VARCHAR(50) NOT NULL,
asset_name VARCHAR(100) NOT NULL,
asset_type VARCHAR(30) NOT NULL,
quantity INT NOT NULL DEFAULT 1,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
location VARCHAR(100),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_assets_asset_code (asset_code)
);
CREATE TABLE asset_allocations (
id BIGINT NOT NULL AUTO_INCREMENT,
ticket_id BIGINT NOT NULL,
asset_id BIGINT NOT NULL,
assignee_id VARCHAR(100),
assignee_tenant_id VARCHAR(100),
allocation_status VARCHAR(20) NOT NULL,
loaned_at TIMESTAMP NULL,
due_at TIMESTAMP NULL,
returned_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT fk_asset_allocations_ticket
FOREIGN KEY (ticket_id) REFERENCES support_tickets(id),
CONSTRAINT fk_asset_allocations_asset
FOREIGN KEY (asset_id) REFERENCES assets(id)
);
CREATE TABLE vehicle_schedules (
id BIGINT NOT NULL AUTO_INCREMENT,
ticket_id BIGINT NOT NULL,
asset_id BIGINT NOT NULL,
departure_at TIMESTAMP NOT NULL,
arrival_at TIMESTAMP NULL,
destination VARCHAR(255),
driver_name VARCHAR(100),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT fk_vehicle_schedules_ticket
FOREIGN KEY (ticket_id) REFERENCES support_tickets(id),
CONSTRAINT fk_vehicle_schedules_asset
FOREIGN KEY (asset_id) REFERENCES assets(id)
);
CREATE TABLE remote_support (
id BIGINT NOT NULL AUTO_INCREMENT,
ticket_id BIGINT NOT NULL,
status_code VARCHAR(20) NOT NULL,
support_engineer_id VARCHAR(100),
support_engineer_tenant_id VARCHAR(100),
scheduled_time TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT fk_remote_support_ticket
FOREIGN KEY (ticket_id) REFERENCES support_tickets(id)
);
CREATE TABLE notification_logs (
id BIGINT NOT NULL AUTO_INCREMENT,
ticket_id BIGINT NULL,
recipient_id VARCHAR(100) NOT NULL,
recipient_tenant_id VARCHAR(100) NOT NULL,
channel VARCHAR(20) NOT NULL,
target_address VARCHAR(100),
delivery_status VARCHAR(20) NOT NULL,
fallback_channel VARCHAR(20),
error_message TEXT,
sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT fk_notification_logs_ticket
FOREIGN KEY (ticket_id) REFERENCES support_tickets(id)
);
```
### 6.3 테이블 간 연동 해석
- `workspaces`는 외부 진입 채널과 내부 처리 단위를 연결하는 기준 테이블임.
- `workspace_channel_mappings`는 하나의 `workspace`가 어떤 ABC 채널을 사용하는지 정의함.
- `workspace_field_mappings`는 Next.js 동적 폼과 ABC 커스텀 필드를 동일한 규칙으로 묶어줌.
- `support_tickets`는 우리 시스템이 상태와 권한을 제어하기 위한 내부 티켓 헤더임.
- `abc_feedback_mappings`는 내부 티켓과 ABC 원본 피드백을 1:1로 연결하는 핵심 브릿지 테이블임.
- `migration_mappings`는 기존 시스템 글, 댓글, 첨부가 어느 내부 티켓과 ABC 피드백으로 적재되었는지 추적함.
## 7. API 엔드포인트 설계
### 7.1 API 설계 원칙
- 외부 클라이언트는 직접 ABC API를 호출하지 않고 우리 FastAPI를 경유함.
- FastAPI는 SSO 토큰을 검증하고 `tenant_id`, `workspace`, 권한 범위를 해석함.
- 등록 시에는 ABC API와 자체 DB 쓰기가 하나의 업무 트랜잭션처럼 동작해야 함.
- 조회 시에는 우리 DB에서 권한 필터링을 먼저 수행하고, 필요 시 ABC 원본 데이터를 조합하여 응답함.
### 7.2 브릿지 API 목록
| 메서드 | 경로 | 목적 |
| --- | --- | --- |
| `POST` | `/api/workspaces/{workspaceCode}/tickets` | 사용자 문의/신청 등록, ABC 피드백 생성, 내부 티켓 및 매핑 저장 |
| `GET` | `/api/workspaces/{workspaceCode}/tickets` | 권한 필터링된 목록 조회 |
| `GET` | `/api/workspaces/{workspaceCode}/tickets/{ticketId}` | 내부 상태 + ABC 원본 상세 조합 조회 |
| `POST` | `/api/workspaces/{workspaceCode}/tickets/{ticketId}/comments` | 댓글 또는 처리 메모 등록 |
| `POST` | `/api/workspaces/{workspaceCode}/tickets/{ticketId}/attachments` | 첨부 업로드 브릿지 처리 |
| `GET` | `/api/workspaces/{workspaceCode}/form-template` | 동적 폼 렌더링용 필드 구성 조회 |
| `GET` | `/api/admin/workspaces/{workspaceCode}/pending-approvals` | 운영 페이지 승인 대기 목록 조회 |
| `POST` | `/api/admin/tickets/{ticketId}/approve` | 승인 처리 및 `request_approvals` 기록 |
| `POST` | `/api/admin/tickets/{ticketId}/reject` | 반려 처리 및 반려 사유 기록 |
| `POST` | `/api/admin/tickets/{ticketId}/follow-up/assets` | 자산 배정 후속 처리 |
| `POST` | `/api/admin/tickets/{ticketId}/follow-up/vehicle` | 차량 배차 후속 처리 |
### 7.3 등록 브릿지 API 처리 순서
```mermaid
flowchart LR
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.2px,color:#0f172a;
classDef control fill:#f7f7f7,stroke:#334155,stroke-width:1.2px,color:#111827;
C1[클라이언트 요청]:::result -->|SSO 토큰 포함| C2[FastAPI 브릿지]:::control
C2 -->|workspace 및 권한 확인| C3[내부 사용자·역할 및 user_workspace_access 조회]:::result
C2 -->|필드 매핑 로드| C4[workspace_field_mappings 조회]:::result
C2 -->|ABC payload 변환| C5[ABC POST /feedbacks 호출]:::control
C5 -->|feedback_id 반환| C6[ABC 원본 저장 완료]:::result
C2 -->|내부 티켓 생성| C7[support_tickets 저장]:::result
C2 -->|ABC 연결 기록| C8[abc_feedback_mappings 저장]:::result
C2 -->|승인 필요 여부 판정| C9[초기 상태 결정]:::result
```
### 7.4 조회 API 권한 필터링 규칙
| 시나리오 | 필터 기준 |
| --- | --- |
| 일반 사용자 내 글 조회 | `requester_id = current_user_id` and `requester_tenant_id = current_tenant_id` |
| 워크스페이스 담당자 조회 | `user_workspace_access.can_manage = true` |
| 승인 담당자 조회 | `user_workspace_access.can_approve = true` and `page_scope` 일치 |
| 외부 고객 조회 | 본인 글만 조회, 비밀글은 작성자/담당자만 허용 |
### 7.5 예시 응답 모델
```json
{
"ticketId": 1024,
"workspaceCode": "INTRA_BOOK_REQUEST",
"statusCode": "PENDING_APPROVAL",
"approvalRequired": true,
"abc": {
"channelId": "book-request-channel",
"feedbackId": "fb_938421",
"detailUrl": "https://abc.example.com/feedbacks/fb_938421"
},
"requester": {
"userId": "u1001",
"tenantId": "baron"
}
}
```
## 8. 마이그레이션 매핑 로직 설계
### 8.1 마이그레이션 목표
기존 시스템의 게시글, 신청서, 댓글, 첨부를 ABC 원본 구조로 적재하면서, 동시에 우리 시스템의 내부 티켓과 매핑 정보를 남겨 이후 조회, 권한 제어, 운영 처리에 문제없이 연결되도록 해야 함.
### 8.2 배치 처리 단계
1. 기존 시스템에서 대상 데이터와 첨부 메타데이터를 추출함.
2. 기존 글 유형을 `workspace``ticket_type`으로 변환함.
3. 필드 매핑 규칙에 따라 ABC 커스텀 필드 payload를 생성함.
4. ABC API로 게시글 원본을 생성하고 `abc_feedback_id`를 수신함.
5. 우리 DB의 `support_tickets`에 내부 제어용 티켓을 생성함.
6. `abc_feedback_mappings``migration_mappings`에 연결 정보를 저장함.
7. 댓글과 첨부는 원본 글 적재 완료 후 후속 단계로 연결함.
8. 실패 건은 `migration_status = 'FAILED'``error_message`로 남기고 재처리 가능하게 함.
### 8.3 마이그레이션 흐름 다이어그램
```mermaid
flowchart TD
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.2px,color:#0f172a;
classDef control fill:#f7f7f7,stroke:#334155,stroke-width:1.2px,color:#111827;
M1[기존 시스템 데이터]:::result -->|배치가 원본 추출| M2[Migration Script]:::control
M2 -->|workspace 규칙 적용| M3[workspace 결정 결과]:::result
M2 -->|ABC payload 생성| M4[ABC POST /feedbacks]:::control
M4 -->|abc_feedback_id 수신| M5[ABC 원본 생성 결과]:::result
M2 -->|내부 티켓 생성| M6[support_tickets]:::result
M2 -->|브릿지 연결 저장| M7[abc_feedback_mappings]:::result
M2 -->|이관 추적 저장| M8[migration_mappings]:::result
M2 -->|실패 사유 기록| M9[재처리 대상 목록]:::result
```
### 8.4 마이그레이션 스크립트 의사코드
```python
def migrate_record(source_record):
workspace = resolve_workspace(source_record.app_id, source_record.service_type)
template = load_workspace_template(workspace.id)
abc_payload = build_abc_payload(source_record, template)
abc_feedback = abc_client.create_feedback(
channel_id=template.abc_channel_id,
payload=abc_payload,
)
ticket = create_support_ticket(
workspace_id=workspace.id,
requester_id=source_record.user_id,
requester_tenant_id=source_record.tenant_id,
ticket_type=source_record.ticket_type,
title=source_record.title,
status_code=initial_status(source_record),
)
create_abc_feedback_mapping(
ticket_id=ticket.id,
workspace_id=workspace.id,
abc_channel_id=template.abc_channel_id,
abc_feedback_id=abc_feedback.id,
)
create_migration_mapping(
source_system="legacy_support",
source_entity_type="POST",
source_entity_id=source_record.legacy_id,
workspace_id=workspace.id,
ticket_id=ticket.id,
abc_feedback_id=abc_feedback.id,
migration_status="SYNCED",
)
```
### 8.5 운영상 주의점
- 댓글과 첨부는 원본 글 생성 성공 이후 단계적으로 적재해야 함.
- 기존 시스템 ID 중복을 막기 위해 `migration_mappings`에 유니크 제약이 필요함.
- 재실행 가능성을 고려해 배치는 멱등적으로 설계해야 함.
- ABC 적재 성공 후 내부 DB 저장 실패가 발생하면 보상 처리 또는 재동기화 큐가 필요함.
## 9. 정리
이 설계의 핵심은 S/W별 Q&A와 인트라넷 사내 지원을 하나의 `workspace` 기반 플랫폼으로 통합하되, 시스템 역할을 명확히 나누는 데 있음.
- ABC User Feedback는 게시글 원본 저장소이자 채널/필드 관리 도구임.
- 우리 시스템은 FastAPI와 자체 DB를 통해 권한 분기, 승인 워크플로우, 운영 페이지, 동적 폼, 외부 연동을 담당함.
- BARON-SSO는 사용자와 `tenant_id`의 원본 인증 계층으로 유지됨.
- `abc_feedback_mappings`, `workspace_channel_mappings`, `migration_mappings`가 두 시스템을 연결하는 핵심 브릿지 역할을 수행함.
다음 단계에서는 상태 코드 표준값, `page_scope` 체계, 실제 ABC API 스펙, 운영 페이지 화면 와이어프레임을 추가하면 구현 준비 수준의 설계 문서로 확장 가능함.
@@ -0,0 +1,228 @@
# BARON-SSO 권한 및 접근 제어 설계
## 1. 문서 목적
본 문서는 BARON-SSO 연동 시점에 필요한 역할 분리, 테넌트 기반 1차 분기, 로그인 후 페이지 분기, 프로젝트 접근 제어 규칙을 별도로 정리한 문서임.
핵심 목적은 다음과 같음.
- 개발자, 프로젝트 관리자, 사용자의 권한 범위를 명확히 구분함.
- BARON-SSO `tenant_id` 와 우리 시스템의 페이지 분기 기준을 연결함.
- BARON-SSO는 인증과 현재 테넌트 정보 제공까지만 담당하고, 최종 권한은 내부 DB에서 관리한다는 원칙을 고정함.
- 프로젝트, 문의 구분, 관리자 콘솔 처리 구조를 현재 구현 방향 기준으로 문서화함.
- 이후 실제 SSO 구현, 라우팅, 내부 권한 테이블 설계의 기준 문서로 사용함.
## 2. 적용 대상 범위
- 인증 원본: BARON-SSO
- 사용자 화면: `/support/[workspaceCode]/new`, `/support/[workspaceCode]/list`, `/support/[workspaceCode]/[ticketId]`
- 관리자 콘솔: `/main/project/[projectId]/feedback?channelId=[channelId]`
- 운영 보조 화면: `/ops`, `/admin/issues`
- 제어 백엔드: `apps/secretary-api`
- 관리자 API: `apps/api`
## 3. 역할 정의
| 역할 | 영문 역할명 | 주요 권한 | 접근 범위 | 로그인 후 기본 진입 화면 |
| ------ | ------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- | --------------------------------- |
| 개발자 | `SYSTEM_ADMIN`, `SUPER_ADMIN` | 시스템 전체 설정, SSO 연동 관리, 프로젝트 생성 및 삭제, 권한 정책 변경 | 모든 프로젝트, 모든 관리자 기능 | 관리자 콘솔 또는 시스템 설정 화면 |
| 관리자 | `PROJECT_MANAGER` | 배정된 프로젝트의 피드백 조회, 댓글 처리, 이슈 연동, 권한 설정, 운영 분류 처리 | 내부 DB에 배정된 프로젝트 | 관리자 콘솔 |
| 사용자 | `END_USER`, `FEEDBACK_PROVIDER` | 피드백 작성, 본인 글 조회, 본인 글 상태 확인, 비밀글 작성, 공개된 Q&A 확인 | 본인이 진입한 프로젝트와 본인 작성 데이터 | 사용자 피드백 작성 페이지 |
## 4. 권한 모델 핵심 원칙
### 4.1 1차 판정 기준
- 로그인 자체는 모두 BARON-SSO에서 처리함.
- 우리 시스템은 BARON-SSO 세션 또는 userinfo 기준으로 최소 `sub` 또는 내부 매핑 가능한 식별자와 `tenant_id` 를 받음.
- `tenant_id` 는 현재 사용자가 어떤 테넌트 문맥으로 진입했는지 확인하는 1차 분기 힌트로만 사용함.
### 4.2 2차 판정 기준
- 로그인 성공 직후 내부 DB에서 사용자 매핑과 역할을 조회함.
- 관리자 콘솔 진입 여부는 BARON-SSO 역할이 아니라 내부 DB의 프로젝트 관리자 권한으로 판단함.
- 그 외 사용자는 일반 사용자로 처리함.
- 추가 세부 권한은 우선 `user + project + role` 조합으로 제한하고, 채널 단위 권한은 추후 필요 시 확장함.
### 4.3 내부 권한 DB 책임 분리
- BARON-SSO는 인증과 `sso_sub`, `tenant_id` 등 사용자 식별 문맥만 제공함.
- `baron_support``support_users`, `support_roles`, `support_role_assignments`가 Secretary 사용자와 최종 권한의 원본임.
- `user_workspace_access`는 역할 할당에 따른 workspace별 유효 권한을 저장하는 실행용 접근표임.
- ABC User Feedback의 `users`, `roles`, `members`는 ABC 자체 피드백/관리자 콘솔 CRUD의 권한만 담당함.
- Secretary의 일반 사용자/운영 권한을 ABC의 `users.type`, `roles`, `members`에서 조회하거나 추론하지 않음.
### 4.4 3차 판정 기준
- 관리자 콘솔에 진입한 이후에는 프로젝트별 접근 권한을 다시 확인함.
- 초기에는 전체 프로젝트 관리자만 두고, 채널 관리자는 운영 요구가 생길 때 별도 역할로 확장함.
- 관리자 권한 설정은 관리자 콘솔의 별도 설정 페이지에서 처리함.
## 5. 테넌트 전략
### 5.1 테넌트 사용 원칙
- BARON-SSO의 현재 테넌트 정보는 로그인 문맥과 초기 이동 경로를 정하는 보조 정보로 사용함.
- 테넌트 자체를 권한의 최종 저장소로 사용하지 않음.
- 관리자 여부와 프로젝트 접근 권한은 내부 DB에서 최종 판정함.
### 5.2 테넌트 기반 분기 규칙
| BARON-SSO 테넌트 상태 | 기본 사용자 분류 | 기본 이동 경로 | 추가 분기 |
| ----------------------- | ---------------------------------- | -------------------------------------- | --------------------------- |
| 관리자 테넌트 문맥 | 내부 DB 확인 후 관리자 또는 사용자 | 관리자 콘솔 후보 경로 또는 사용자 경로 | 내부 DB 권한으로 최종 판정 |
| 일반 사용자 테넌트 문맥 | 사용자 | 프로젝트별 피드백 작성 페이지 | 본인 글 목록/상세 접근 허용 |
## 6. 로그인 후 페이지 분기 정책
### 6.1 관리자 후보 사용자
- 로그인 성공 후 내부 DB에서 프로젝트 관리자 권한이 확인되면 관리자 콘솔로 이동함.
- 기본 진입 후보 경로는 다음과 같음.
- `/main/project/[projectId]/feedback?channelId=[channelId]`
- 필요 시 운영 보조 화면 `/ops`, `/admin/issues` 로 이동 가능
- 이후 프로젝트별 권한에 따라 진입 가능한 콘솔 페이지를 다시 제한함.
### 6.2 일반 사용자
- 로그인 성공 후 각 소프트웨어 또는 서비스가 지정한 피드백 작성 페이지로 이동함.
- 진입 URL에는 프로젝트/채널에 대응되는 `workspaceCode` 또는 서비스 식별자가 함께 전달됨.
- 예시
- `/support/EGBIM/new`
- `/support/TOVA/new`
- `/support/GAIA/new`
- `/support/KNGIL/new`
- `/support/INTRANET_QNA/new`
## 7. 프로젝트 및 채널 초기 운영 구조
### 7.1 초기 프로젝트 생성 대상
| 프로젝트명 | 설명 | 최초 기본 채널 |
| -------------- | ----------------- | -------------- |
| `EGBIM` | EGBIM 전용 Q&A | `EGBIM` |
| `TOVA` | TOVA 전용 Q&A | `TOVA` |
| `GAIA` | GAIA 전용 Q&A | `GAIA` |
| `KNGIL` | KNGIL 전용 Q&A | `KNGIL` |
| `INTRANET_QNA` | 인트라넷 공통 Q&A | `INTRANET_QNA` |
### 7.2 채널 운영 확장 계획
- 최초에는 프로젝트당 채널 1개로 시작함.
- 이후 각 프로젝트 내부에서 채널을 별도로 확장하여 문의 유형별로 분리 관리함.
- 예시
- `EGBIM > 일반 문의`, `EGBIM > 장애 문의`, `EGBIM > 기능 개선`
- `INTRANET_QNA > 계정 문의`, `INTRANET_QNA > 권한 문의`, `INTRANET_QNA > 시스템 오류`
## 8. 사용자 진입 구조
### 8.1 기본 시나리오
1. 사용자는 각 소프트웨어에서 이미 BARON-SSO 로그인 상태임.
2. 사용자가 Q&A 이동 버튼을 클릭함.
3. 소프트웨어는 자기 프로젝트에 대응되는 Q&A 진입 URL로 이동시킴.
4. 우리 시스템은 세션에서 SSO 식별자와 `tenant_id` 를 읽음.
5. 내부 DB에서 사용자와 프로젝트 관리자 권한을 조회함.
6. 일반 사용자면 해당 프로젝트의 작성 페이지로 이동함.
7. 프로젝트 관리자면 관리자 콘솔로 이동함.
### 8.2 프로젝트별 사용자 진입 예시
| 진입 서비스 | 사용자 이동 경로 | 프로젝트 관리자 이동 경로 |
| ------------ | --------------------------- | -------------------------------------------------------------------------- |
| EGBIM | `/support/EGBIM/new` | `/main/project/[egbimProjectId]/feedback?channelId=[egbimChannelId]` |
| TOVA | `/support/TOVA/new` | `/main/project/[tovaProjectId]/feedback?channelId=[tovaChannelId]` |
| GAIA | `/support/GAIA/new` | `/main/project/[gaiaProjectId]/feedback?channelId=[gaiaChannelId]` |
| KNGIL | `/support/KNGIL/new` | `/main/project/[kngilProjectId]/feedback?channelId=[kngilChannelId]` |
| 인트라넷 Q&A | `/support/INTRANET_QNA/new` | `/main/project/[intranetProjectId]/feedback?channelId=[intranetChannelId]` |
## 9. 문의 작성과 관리자 콘솔 분기 구조
### 9.1 기본 원칙
- 사용자는 프로젝트 안에서 글을 작성함.
- 글 작성 시 `문의 구분` 값을 함께 선택함.
- 글 작성 시 필요하면 `비밀글` 여부를 함께 선택함.
-`문의 구분` 은 장기적으로 채널 또는 내부 `workspace` 분기 기준으로 사용함.
- 관리자 콘솔에서는 프로젝트 단위로 유입 건을 구분하여 관리하고, 비밀글은 권한 있는 관리자만 열람 가능하도록 제한함.
### 9.2 현재 구조와 향후 확장 방향
| 항목 | 현재 기준 | 향후 확장 방향 |
| ----------- | ------------------------- | -------------------------------- |
| 프로젝트 | 서비스 단위 분리 | 유지 |
| 채널 | 프로젝트당 1개 기본 채널 | 필요 시 확장 |
| 문의 구분 | 사용자 작성 폼의 선택 값 | 운영 큐 분기 기준으로 사용 |
| 비밀글 | 작성 시 boolean 값 저장 | 역할별 조회 제한과 감사로그 추가 |
| 관리자 화면 | 프로젝트 단위 피드백 목록 | 프로젝트/문의구분 기반 다중 큐 |
## 10. 현재 구현 구조와의 연결 기준
### 10.1 사용자 화면
- 현재 사용자용 지원 화면은 `apps/web/src/pages/support/**` 경로에 구성되어 있음.
- 사용자 상세에서는 관리자 이슈 상태, 댓글, 본인 글 수정/삭제를 제공함.
- 사용자 상세 상태 표시는 현재 ABC 원본 피드백의 연결 이슈 상태를 읽어 반영하도록 확장됨.
### 10.2 관리자 화면
- 실제 관리자 콘솔은 `apps/web/src/pages/main/project/[projectId]/feedback.tsx` 경로를 중심으로 동작함.
- 피드백 상세 시트에서 댓글 CRUD와 이슈 연결 상태를 확인할 수 있도록 연계됨.
- 관리자 콘솔에서 이슈 연결 시 `apps/api``apps/secretary-api` 사이에서 내부 `support_tickets.issue_link_status` 를 함께 동기화하도록 보강됨.
- 관리자 권한 설정 페이지는 관리자 콘솔 메뉴 항목으로 추가하는 방향을 기준으로 함.
### 10.3 제어 백엔드
- `apps/secretary-api` 는 사용자 화면용 상태, 댓글, 내부 티켓 메타데이터를 관리함.
- `apps/api` 는 ABC 관리자 콘솔의 피드백/이슈 기능을 제공함.
- 장기적으로는 BARON-SSO 로그인 완료 후 내부 DB 권한 조회 결과를 기준으로 사용자 경로와 관리자 경로를 라우팅하는 정책 계층이 추가되어야 함.
## 11. 권한 처리 시퀀스
```mermaid
flowchart TD
A[사용자 또는 관리자\nBARON-SSO 로그인 상태] --> B[Q&A 이동 버튼 클릭]
B --> C[우리 시스템 진입]
C --> D[세션에서 SSO 식별자와 tenant_id 확인]
D --> E[내부 DB에서 사용자와 프로젝트 관리자 권한 조회]
E --> F{프로젝트 관리자 권한 존재?}
F -- 예 --> G[관리자 콘솔 진입]
F -- 아니오 --> H[사용자 작성 페이지 진입]
G --> I{프로젝트 접근 권한 존재?}
I -- 예 --> J[프로젝트별 관리자 콘솔 페이지 진입]
I -- 아니오 --> K[권한 없음 또는 다른 프로젝트로 재분기]
H --> L[프로젝트별 사용자 작성 페이지 이동]
L --> M[피드백 작성]
M --> N[문의 구분 값과 비밀글 여부 저장]
N --> O[프로젝트/문의구분 기준 관리자 콘솔 큐 반영]
```
## 12. 권한 매핑 테이블 초안
| 구분 | BARON-SSO 값 | 우리 시스템 해석 | 화면 권한 | 데이터 권한 |
| --------------- | ------------------------------ | -------------------------------------- | --------------------------------------------- | ------------- |
| 시스템 관리자 | 사용자 식별 정보 + tenant 문맥 | 내부 DB의 `SUPER_ADMIN` | 전체 관리자 콘솔, 설정, 프로젝트 관리 | 전체 프로젝트 |
| 프로젝트 관리자 | 사용자 식별 정보 + tenant 문맥 | 내부 DB의 `PROJECT_MANAGER` | 담당 프로젝트 콘솔, 권한 설정, 댓글/이슈 처리 | 배정 프로젝트 |
| 일반 사용자 | 사용자 식별 정보 + tenant 문맥 | 내부 DB 일반 사용자 또는 미승인 사용자 | 사용자 작성/목록/상세 | 본인 작성 글 |
## 13. 구현 시 체크리스트
- BARON-SSO에서 내부 사용자 매핑에 사용할 식별 claim 확정
- 프로젝트 `EGBIM`, `TOVA`, `GAIA`, `KNGIL`, `INTRANET_QNA` 생성
- 각 프로젝트에 동일명 기본 채널 1개 생성
- 프로젝트별 관리자 접근 정책 정의
- 사용자 Q&A 이동 버튼의 프로젝트별 URL 매핑 정의
- 로그인 후 내부 DB 권한 조회 기반 관리자/사용자 라우팅 구현
- 문의 구분 값과 채널 분기 규칙 설계
- 비밀글 저장, 조회 제한, 관리자 열람 규칙 설계
- 프로젝트별 확장 채널 생성 전략 수립
## 14. 최종 정리
- BARON-SSO는 인증과 현재 테넌트 문맥 제공 시스템임.
- 최종 권한과 관리자 여부는 내부 DB가 결정함.
- 일반 사용자는 프로젝트별 피드백 작성 페이지로 이동함.
- 프로젝트 관리자는 담당 프로젝트의 관리자 콘솔로 이동함.
- 초기에는 프로젝트당 채널 1개와 프로젝트 관리자 역할만 두고, 이후 필요 시 채널 관리자와 세분화 권한을 확장함.
- 비밀글은 사용자 작성 기능과 관리자 조회 제한 정책에 포함해야 함.
- 현재 구현된 사용자 화면, 관리자 콘솔, 내부 티켓/댓글/이슈 상태 동기화 구조는 이 권한 설계 문서를 기준으로 다음 단계 SSO 연동과 내부 권한 구현으로 연결할 수 있음.
@@ -0,0 +1,499 @@
# 사내 지원 플랫폼 환경 셋업 및 Task 목록
## 1. 문서 목적
본 문서는 [architecture_secretary_sso_user_scenarios.md](./architecture_secretary_sso_user_scenarios.md) 에서 정리한 사용자 시나리오와 시스템 역할 분담을 실제 개발 환경으로 옮기기 위한 실행 계획 문서임.
권한, 테넌트, 로그인 후 페이지 분기 설계는 [architecture_secretary_sso_role_access.md](./architecture_secretary_sso_role_access.md) 에 별도 정리함.
핵심 목적은 다음과 같음.
- 어떤 순서로 환경을 셋업해야 하는지 명확히 정리함.
- 개발 착수 전에 필요한 선행 정보와 의존 항목을 체크함.
- 사용자 화면, 운영 화면, 관리자 기능, 외부 연동까지 단계별로 작업을 분해함.
- 시연과 운영 전환을 위한 검증 항목을 체크리스트로 관리함.
## 2. 기본 원칙
- 먼저 ABC User Feedback 기본 환경을 안정적으로 띄움.
- 그 다음 BARON-SSO, 우리 시스템 백엔드, 자체 DB를 연결함.
- 이후 사용자 화면, 운영 화면, 관리자 기능 연계를 붙임.
- 마지막으로 시연 기준 end-to-end 검증을 수행함.
## 2.1 현재 작업 기준선
- 프로젝트/채널 구조 확정과 BARON-SSO `tenant_id` 기반 권한 분기 정책은 최종 완료의 선행 조건으로 유지함.
- 다만 현재 주차에서는 위 선행 조건이 아직 확정되지 않았으므로, 우선 범위를 `프론트 UI 구성 + 우리 시스템 백엔드/DB 구축`까지로 제한함.
- 다음 주 작업 범위는 `BARON-SSO 로그인 연동 + 권한 분기 + 페이지 분기 처리`로 계획함.
- 따라서 현재 문서의 `(완료)` 표시는 SSO/권한 확정 이전에도 독립적으로 검증 가능한 UI, 라우팅, 스텁 API, DB 스키마, 로컬 실행 항목에만 부여함.
## 3. 전체 Task 로드맵
| 단계 | 작업 묶음 | 핵심 목표 | 완료 기준 |
| --- | --- | --- | --- |
| 1 | 로컬 기본 환경 구성 | ABC, DB, 개발 도구를 실행 가능한 상태로 만듦 | 로컬에서 ABC Web/API 접속 가능 |
| 2 | ABC 운영 구조 셋업 | 프로젝트, 채널, 필드, 역할 구조를 준비함 | 서비스별 채널/필드/권한 정책 초안 반영 완료 |
| 3 | BARON-SSO 연동 준비 | 로그인과 사용자 식별 체계를 연결함 | `user_id`, `tenant_id`, 역할 정보를 세션에서 읽을 수 있음 |
| 4 | 우리 시스템 백엔드/DB 셋업 | 내부 티켓/승인/매핑 저장소를 준비함 | `support_tickets`, `request_approvals`, `abc_feedback_mappings` 저장 가능 |
| 5 | 사용자 화면 셋업 | 작성, 목록, 상세 흐름을 연결함 | 사용자 기준 등록/조회 시연 가능 |
| 6 | 운영/관리 화면 셋업 | 승인, 이슈 생성, 처리 결과 입력 흐름을 연결함 | 운영자/관리자 기준 처리 시연 가능 |
| 7 | 외부 연동/알림 준비 | 알림 및 업무 시스템 연계를 준비함 | Mock 또는 실제 연동 경로 확인 완료 |
| 8 | 통합 검증/시연 준비 | 전체 흐름을 점검하고 시연용 데이터를 고정함 | end-to-end 시연 체크리스트 통과 |
## 4. 단계별 상세 Task
### 4.1 로컬 기본 환경 구성
- 작업 기준 저장소: `abcfeedback_test`
- 현재 확인된 로컬 기동 방식: `./start-local.sh` 또는 `docker compose -f docker/docker-compose.yml up -d`
- 선행 확인 항목
- Docker CLI 설치 및 Docker daemon 접근 가능 여부 확인
- Node.js / pnpm 설치 여부 확인
- 로컬 포트 사용 여부 확인: `3001`, `4000`, `5080`, `13306`
- 현재 확인된 기본 접속 URL
- Web UI: `http://localhost:3001`
- API Docs: `http://localhost:4000/docs`
- API Health: `http://localhost:4000/api/health`
- SMTP Test Inbox: `http://localhost:5080`
- 현재 확인된 기본 DB 접속 정보
- DB Engine: MySQL 8.0
- Host: `localhost`
- Port: `13306`
- Database: `userfeedback`
- Username: `userfeedback`
- Password: `userfeedback`
- 현재 확인된 핵심 환경 변수
- Web: `NEXT_PUBLIC_API_BASE_URL=http://localhost:4000`
- API: `JWT_SECRET`, `MYSQL_PRIMARY_URL`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_SENDER`
- 1차 실행 절차
- `cd abcfeedback_test`
- `./start-local.sh` `(완료)`
- `./check-local.sh` `(완료)`
- 기동 후 첫 진입 절차
- 테넌트 생성
- 관리자 계정 생성
- 로그인
- Project 생성
- Channel 생성
- API Key 생성
- 첫 피드백 등록
- 현재 작업 환경 확인 결과
- Docker daemon 기동 후 `start-local.sh`, `check-local.sh` 기준 로컬 컨테이너 실행과 기본 헬스체크 확인 `(완료)`
- Web `3001`, API `4000`, SMTP UI `5080`, MySQL `13306` 포트 매핑 확인 `(완료)`
- 산출물
- 로컬 실행 스크립트 및 접속 URL 문서화 완료
- 4.2 진행 전 선행 조건: ABC Web/API Health check 성공 확인
### 4.2 ABC 운영 구조 셋업
- 설계 기준 문서
- `docs/architecture_secretary_sso_components_v2.md`
- `docs/architecture_secretary_sso_user_scenarios.md`
- 운영 구조 설계 원칙
- ABC Project는 서비스 운영 단위로 생성하고, 세부 신청/문의 유형은 Channel로 분리함
- 외부 진입 식별자 `app_id`, `service_type_id`, 메뉴 코드는 우리 시스템에서 `workspace`로 해석하고, `workspace_channel_mappings`로 ABC Channel에 연결함
- 일반 사용자 입력 스키마는 우리 시스템 동적 폼과 ABC 커스텀 필드를 동시에 맞춰야 하므로 `workspace_field_mappings` 기준으로 관리함
- 역할과 화면 권한은 BARON-SSO 세션을 기준으로 판정하되, ABC 프로젝트 접근 권한은 별도로 부여함
- 1차 운영 구조 초안
- Project 단위
- `INTRANET_SUPPORT`: 인트라넷 기반 사내 지원 업무 공통 Project
- `SOFTWARE_QA`: S/W 프로그램별 문의 접수 공통 Project
- Channel 단위
- `INTRA_SUPPLIES_REQUEST`: 물품 신청
- `INTRA_BOOK_REQUEST`: 도서 신청
- `INTRA_VEHICLE_REQUEST`: 출장 차량 신청
- `INTRA_EQUIPMENT_RENTAL`: 비품 대여
- `INTRA_GENERAL_QNA`: 사내 문의
- `SW_APP_<APP_CODE>_QNA`: 앱별 전용 Q&A 채널
- Field 스키마 초안
- 공통 필드: `request_category`, `title`, `description`, `requester_contact`, `attachment`
- 승인형 업무 필드: `approval_required`, `approver_org`, `requested_date`, `priority`
- 자산/물품형 필드: `asset_type`, `quantity`, `usage_purpose`, `delivery_location`
- 도서형 필드: `book_title`, `author`, `publisher`, `purchase_reason`
- 차량형 필드: `departure_date`, `return_date`, `destination`, `passenger_count`
- Q&A형 필드: `app_version`, `environment`, `error_message`, `expected_result`
- 역할 정의 초안
- `ROLE_USER`: 일반 사용자 작성/본인 조회
- `ROLE_APPROVER`: 승인 대기 조회, 승인/반려 처리
- `ROLE_OPERATOR`: 전체 운영 목록 조회, 상태 변경, 이슈 연결
- `ROLE_ADMIN`: Project/Channel/Field/Member 관리
- 멤버/권한 부여 정책
- Project 멤버는 최소 `승인자`, `운영 담당자`, `관리자` 그룹으로 시작함
- Channel 단위 운영은 가능하면 Project Role로 통일하고, 예외 채널만 추가 권한을 부여함
- 운영 화면 접근 권한과 ABC 관리자 권한은 동일 인원 기준으로 시작하되, 이후 분리 가능하게 설계함
- 1차 실무 작업 순서
- `INTRANET_SUPPORT`, `SOFTWARE_QA` Project 생성
- 인트라넷용 기본 Channel 5종 생성
- 시범 앱 1개를 선정해 `SW_APP_<APP_CODE>_QNA` Channel 생성
- Channel별 필드 스키마 반영
- Project Role 생성 및 멤버 배정
- `workspace_channel_mappings`, `workspace_field_mappings` 초안 작성
- seed 데이터 준비 항목
- 테스트용 일반 사용자 1명, 승인자 1명, 운영 담당자 1명, 관리자 1명
- 인트라넷 업무 5종에 대한 샘플 Channel/Field 정의서
- 앱 Q&A 1종에 대한 샘플 Channel/Field 정의서
- `workspace` 매핑 초안 표
| workspace_code | 진입 구분 | ABC Project | ABC Channel | 주요 Field 그룹 | 승인 필요 기본값 |
| --- | --- | --- | --- | --- | --- |
| `INTRA_SUPPLIES_REQUEST` | 인트라넷 물품 신청 | `INTRANET_SUPPORT` | `INTRA_SUPPLIES_REQUEST` | 공통 + 승인형 + 자산/물품형 | 예 |
| `INTRA_BOOK_REQUEST` | 인트라넷 도서 신청 | `INTRANET_SUPPORT` | `INTRA_BOOK_REQUEST` | 공통 + 승인형 + 도서형 | 예 |
| `INTRA_VEHICLE_REQUEST` | 인트라넷 차량 신청 | `INTRANET_SUPPORT` | `INTRA_VEHICLE_REQUEST` | 공통 + 승인형 + 차량형 | 예 |
| `INTRA_EQUIPMENT_RENTAL` | 인트라넷 비품 대여 | `INTRANET_SUPPORT` | `INTRA_EQUIPMENT_RENTAL` | 공통 + 승인형 + 자산/물품형 | 예 |
| `INTRA_GENERAL_QNA` | 인트라넷 일반 문의 | `INTRANET_SUPPORT` | `INTRA_GENERAL_QNA` | 공통 + Q&A형 | 아니오 |
| `SW_APP_<APP_CODE>_QNA` | S/W 프로그램별 문의 | `SOFTWARE_QA` | `SW_APP_<APP_CODE>_QNA` | 공통 + Q&A형 | 아니오 |
- `workspace_field_mappings` 작성 규칙 초안
- 모든 `workspace`는 공통 필드 `request_category`, `title`, `description`, `requester_contact`, `attachment`를 기본 포함함
- 승인형 업무는 `approval_required=true`를 기본값으로 두고, 승인자 조직/우선순위/희망일자를 추가 매핑함
- Q&A형 업무는 승인 필드 대신 `app_version`, `environment`, `error_message`, `expected_result`를 우선 매핑함
- ABC 커스텀 필드 키는 가능하면 `snake_case`로 고정하고, 우리 시스템 `local_field_code`와 동일한 이름을 사용함
- 첨부는 ABC 첨부 기능을 기본 저장소로 사용하고, 우리 시스템에는 첨부 메타데이터와 내부 티켓 연결 정보만 저장함
- 완료 기준
- 서비스별 Project/Channel/Field/Role 초안이 문서로 정리되어 있어야 함
- 최소 1개 인트라넷 업무와 1개 S/W 앱 Q&A에 대해 실제 ABC 생성 대상 목록이 확정되어 있어야 함
- 현재 메모
- 이 단계는 프론트 UI/백엔드 스텁 완료와 별개로 아직 진행중이며, Project/Channel 구조가 확정되어야 최종 완료 처리 가능함
### 4.3 BARON-SSO 연동 준비
- 확인된 전제
- ABC User Feedback는 커스텀 OAuth 2.0 / OIDC 제공자 연동을 지원함
- 현재 ABC Web의 OAuth 콜백 경로는 `/auth/oauth-callback` 기준으로 동작함
- 현재 Web 외부 노출 포트는 로컬 기준 `3001`이므로, ABC 관리자 UI를 그대로 사용할 경우 개발용 콜백 URI 후보는 `http://localhost:3001/auth/oauth-callback`
- 확보해야 할 BARON-SSO 클라이언트 등록 정보
- `client_id`
- `client_secret`
- `authorization_endpoint`
- `token_endpoint`
- `userinfo_endpoint`
- 가능하면 `jwks_uri`
- `issuer`
- 지원 `scope` 목록: 최소 `openid`, 사용자 식별, 이메일, 조직/역할 관련 scope
- Redirect / Callback / Logout URI 정리 항목
- ABC 관리자 UI OAuth Callback URI: `/auth/oauth-callback`
- 우리 시스템 사용자 포털 Callback URI: 별도 Next.js 앱 경로 확정 필요
- 우리 시스템 운영 포털 Callback URI: 사용자 포털과 분리 여부 확정 필요
- Logout Redirect URI: BARON-SSO 로그아웃 후 복귀할 공통 URL 확정 필요
- 운영 환경별로 `local`, `dev`, `stg`, `prod` URI를 각각 등록 목록으로 정리해야 함
- 세션 저장 구조 초안
- 인증 원본은 BARON-SSO가 담당하고, 우리 시스템은 세션 또는 JWT에 필요한 최소 클레임만 저장함
- 공통 세션 필드: `user_id`, `tenant_id`, `display_name`, `email`, `role_keys`, `workspace_scopes`, `access_token_exp`, `refresh_token_exp`
- 선택 세션 필드: `org_id`, `org_name`, `employee_no`, `position_code`
- 서버 측 권한 판정은 세션에 저장된 역할 문자열만 신뢰하지 않고, `tenant_id + workspace + 내부 권한 테이블`을 함께 사용함
- 역할 판정 기준 초안
- `ROLE_USER`: 본인 작성/조회만 가능
- `ROLE_APPROVER`: 승인 대기 목록, 승인/반려 가능
- `ROLE_OPERATOR`: 운영 목록, 상태 변경, 이슈 연결 가능
- `ROLE_ADMIN`: Project/Channel/Field/Member 관리 가능
- 역할 판정 순서
- 1차: BARON-SSO 토큰 또는 userinfo에서 조직/그룹/역할 claim 확인
- 2차: 우리 시스템 `user_workspace_access` 기준으로 `workspace`별 실제 권한 확정
- 3차: ABC 프로젝트 멤버십과 운영 화면 접근 권한 동기화
- 예외 처리 정책
- 미로그인: SSO 로그인 페이지로 리다이렉트
- 세션 만료: 재로그인 유도 후 원래 진입 경로 복귀
- 권한 부족: 403 화면 + 요청 가능한 담당 조직 안내
- `workspace` 매핑 없음: 일반 오류가 아니라 운영 설정 누락으로 분류하여 관리자 알림 대상에 포함
- BARON-SSO userinfo 조회 실패: 재시도 1회 후 실패 로그 적재 및 운영 알림
- 1차 실무 작업 순서
- BARON-SSO 담당자에게 클라이언트 등록 요청 템플릿 전달
- 환경별 Redirect/Callback/Logout URI 목록 확정
- 토큰 claim 샘플 수집: `user_id`, `tenant_id`, 조직/역할 관련 필드 확인
- 세션 스키마와 내부 권한 매핑 규칙 정의
- 테스트 계정 준비: 일반 사용자, 승인자, 운영 담당자, 관리자
- 로그인 성공/권한 부족/세션 만료/`workspace` 미매핑 시나리오 검증 항목 작성
- 완료 기준
- BARON-SSO 클라이언트 등록에 필요한 입력값 목록이 문서화되어 있어야 함
- 세션 저장 필드와 역할 판정 규칙이 문서화되어 있어야 함
- 개발 환경 기준 Callback URI와 예외 처리 정책이 확정되어 있어야 함
- 현재 메모
- BARON-SSO 로그인과 `tenant_id` 기반 권한/페이지 분기 처리는 다음 주 구현 범위로 계획함
### 4.4 우리 시스템 백엔드/DB 셋업
- 구현 전 정합성 확인
- 4.4 기준 DB 엔진은 MySQL 8.0으로 고정함
- `architecture_secretary_sso_components_v2.md`의 SQL 초안에는 `SERIAL`, `JSONB` 등 PostgreSQL 스타일 표현이 포함되어 있으므로 실제 구현 전 MySQL 문법으로 변환해야 함
- FastAPI 기본 구조 초안
- 실제 생성 경로: `apps/secretary-api`
- `apps/secretary-api/app/api`: 사용자/운영 API 라우터
- `apps/secretary-api/app/core`: 설정, 보안, 세션, 공통 유틸
- `apps/secretary-api/app/models`: SQLAlchemy 모델 또는 ORM 엔티티
- `apps/secretary-api/app/schemas`: 요청/응답 DTO
- `apps/secretary-api/app/services`: 권한 판정, 티켓 처리, 승인 처리, ABC 브릿지
- `apps/secretary-api/app/repositories`: DB 접근 계층
- `apps/secretary-api/app/integrations/abc`: ABC API 클라이언트
- `apps/secretary-api/app/integrations/sso`: BARON-SSO 토큰 검증 또는 userinfo 연동
- `apps/secretary-api/alembic`: 마이그레이션 파일
- `apps/secretary-api/app/seeds`: 코드성 초기 데이터 및 테스트용 seed 스크립트
- 현재 저장소 기준 확인 결과
- `apps/secretary-api/app/main.py`에 FastAPI 엔트리포인트가 존재함
- `apps/secretary-api/app/api/routes/health.py``/api/health` 엔드포인트가 존재함
- `apps/secretary-api/app/api/routes/tickets.py``GET /api/workspaces`, `GET /api/workspaces/{workspace_code}/form-template`, `POST /api/workspaces/{workspace_code}/tickets`, `GET /api/workspaces/{workspace_code}/tickets`, `GET /api/tickets/{ticket_id}`, `POST /api/tickets/{ticket_id}/approve`, `POST /api/tickets/{ticket_id}/issue` 경로가 존재함 `(완료)`
- `apps/secretary-api/alembic/versions/0001_initial_support_schema.py`에 P0 기준 1차 MySQL 마이그레이션이 존재함
- `apps/secretary-api/alembic/versions/0002_support_ticket_content_fields.py`에 현재 UI가 사용하는 `description`, `approval_status`, `sync_status`, `issue_link_status`, `extra_fields` 컬럼 보강이 반영됨 `(완료)`
- 현재 작업 환경에서는 `localhost:13306` 대신 Docker bridge IP 기준 `DATABASE_URL` override 로 실제 MySQL 마이그레이션과 티켓 생성/조회 검증을 완료함 `(부분 완료)`
- 다만 ABC 연동 클라이언트, seed 스크립트 정리, 환경 공통 DB 접속 방식 정리는 아직 후속 작업으로 남아 있음
- DB 구축 순서
- 1단계: 코드 테이블 및 서비스 마스터 생성
- `support_status_codes`
- `support_category_codes`
- `software_apps`
- `service_types`
- `workspaces`
- 2단계: 권한 및 채널 매핑 테이블 생성
- `user_workspace_access`
- `workspace_channel_mappings`
- `workspace_field_mappings`
- 3단계: 핵심 티켓 및 연동 테이블 생성
- `support_tickets`
- `abc_feedback_mappings`
- `request_approvals`
- `notification_logs`
- `operator_histories` 또는 `ticket_activity_logs`
- 4단계: 확장 업무 테이블 생성
- `assets`
- `asset_allocations`
- `vehicle_schedules`
- `remote_support`
- 5단계: 이관 추적 테이블 생성
- `migration_batches`
- `migration_mappings`
- 핵심 테이블 구현 우선순위
- P0: `workspaces`, `workspace_channel_mappings`, `workspace_field_mappings`
- P0: `support_tickets`, `abc_feedback_mappings`
- P0: `user_workspace_access`, `request_approvals`
- P1: `notification_logs`, `ticket_activity_logs`
- P1: `migration_batches`, `migration_mappings`
- P2: `assets`, `asset_allocations`, `vehicle_schedules`, `remote_support`
- MySQL 변환 규칙 초안
- `SERIAL``BIGINT AUTO_INCREMENT` 또는 `INT AUTO_INCREMENT`로 변환
- `JSONB`는 MySQL `JSON`으로 변환
- `BOOLEAN`은 MySQL `BOOLEAN` 또는 `TINYINT(1)` 기준으로 통일
- `TIMESTAMP DEFAULT CURRENT_TIMESTAMP``updated_at` 자동 갱신 규칙을 명시적으로 넣음
- 코드 테이블과 상태값은 가능하면 `ENUM`보다 참조 테이블 방식을 우선 적용함
- 마이그레이션 도구 및 seed 준비
- Alembic 기반 버전 관리 적용
- 최초 마이그레이션은 서비스 마스터, 권한, 채널 매핑, 핵심 티켓 테이블까지 포함
- seed 1차 범위: 상태 코드, 카테고리 코드, 기본 `workspace`, 기본 Project/Channel 매핑, 테스트 사용자 권한
- seed 2차 범위: 샘플 티켓, 샘플 승인 데이터, 샘플 알림 이력
- ABC API 연동용 서비스 계층 초안
- `ABCProjectService`: Project/Channel 조회 및 캐시
- `ABCFeedbackService`: 피드백 생성, 상세 조회, 첨부 업로드
- `ABCIssueService`: 이슈 생성, 피드백-이슈 연결
- `ABCMemberSyncService`: 운영자/승인자 프로젝트 멤버십 동기화
- 1차 실무 작업 순서
- `apps/secretary-api` FastAPI 프로젝트 뼈대 생성
- MySQL 연결 설정 및 로컬 `.env` 정의
- Alembic 초기화 및 1차 마이그레이션 작성
- 코드 테이블 및 `workspace` 관련 테이블 생성
- `support_tickets`, `abc_feedback_mappings`, `request_approvals` 생성
- 상태 코드 및 `workspace` seed 적재
- `GET /api/workspaces`, `POST /api/workspaces/{workspaceCode}/tickets` 스텁 API 구현
- ABC API 클라이언트 골격 구현
- 티켓 생성 시 ABC 저장 후 내부 매핑 저장하는 트랜잭션 흐름 설계
- 완료 기준
- MySQL 기준 1차 마이그레이션이 적용 가능해야 함
- `workspace`와 ABC Channel 간 기본 매핑 데이터가 적재 가능해야 함
- 내부 티켓 생성과 ABC `feedback_id` 매핑 저장 구조가 확정되어 있어야 함
- 승인 이력 저장 구조와 운영 이력 저장 구조가 확정되어 있어야 함
- 현재 메모
- `baron_support` DB에 실제 마이그레이션 적용과 티켓 생성/목록 조회는 검증했으며, 현재 프런트 `/support` 흐름과 연결되는 최소 실DB 경로는 확보된 상태임
- 추가 검증 메모
- 현재 `apps/web` fallback 경로 기준으로 ABC MySQL `userfeedback.feedbacks` 저장 및 관리자 Feedback 화면 조회까지 검증했음
- `workspaceCode`별로 ABC Channel 분기 저장되며, 채널 필드 스키마 기준으로 `title`/`contents` 또는 `message` 필드값만 저장되도록 정리했음
- 다만 이 문서 기준 완료 조건인 내부 `support_tickets` + `abc_feedback_mappings` 동시 저장을 현재 fallback 경로가 모두 대체하는 것은 아니므로, ABC DB 기준으로는 `Create/Read` 검증 완료, 전체 CRUD 완료로 보기는 이름
### 4.5 사용자 화면 셋업
- 현재 저장소 기준 확인 결과
- 실제 서비스 웹앱은 `apps/web/src` 기준으로 운영되고 있음
- 현재 `apps/web/src/pages/support/[workspaceCode]/new|list|[ticketId]|index` 라우트가 존재함 `(완료)`
- 사용자 화면은 실제 서비스 라우트 기준으로 운영되며, 로컬 dev 서버 `3002`에서 시연 가능함 `(완료)`
- 단, `docker compose`만으로는 `3002`가 열리지 않으며 `apps/web`를 별도 실행해야 함
- 예시: `cd apps/web && PORT=3002 pnpm dev`
- 라우팅 구조 초안
- `/support` 또는 `/portal/support`: 공통 진입 라우터
- `/support/:workspaceCode/new`: 사용자 작성 화면 `(완료)`
- `/support/:workspaceCode/list`: 내 접수 목록 `(완료)`
- `/support/:workspaceCode/:ticketId`: 접수 상세 `(완료)`
- `/support/:workspaceCode`: 기본 진입 시 목록으로 리다이렉트 `(완료)`
- 승인형 업무와 Q&A형 업무는 같은 라우팅 패턴을 사용하되, `workspaceCode`에 따라 폼 템플릿과 후처리만 다르게 적용함
- 공통 로그인 후 화면 분기 규칙
- SSO 로그인 완료 후 진입 파라미터 `app_id`, `service_type_id`, 메뉴 코드 중 하나를 받아 `workspaceCode`로 해석함
- `workspaceCode`가 확정되면 `GET /api/workspaces/{workspaceCode}/form-template`로 필드 구성을 조회함
- 권한이 없으면 작성 화면으로 보내지 않고 403 또는 접근 안내 화면으로 분기함
- 사용자 피드백 작성 페이지 구현 항목
- 공통 입력 필드: 제목, 내용, 첨부 `(완료)`
- `workspace`별 동적 필드 렌더링: 현재는 최소 Q&A 작성형으로 단순화하여 일부만 사용 `(부분 완료)`
- 제출 전 유효성 검사: 필수값, 날짜 범위, 숫자 범위, 첨부 제한
- 제출 시 처리 순서: 우리 시스템 API 호출 -> ABC 저장 -> 내부 `support_tickets` 생성 -> `abc_feedback_mappings` 저장
- 현재 검증 상태: `apps/web` fallback 경로에서 ABC `feedbacks` 저장과 관리자 조회는 확인했으며 `(부분 완료)`, 내부 `support_tickets`/`abc_feedback_mappings`까지 같은 요청에서 항상 저장되는 구조는 secretary-api 기준으로 계속 검증 필요
- 완료 화면에는 `ticket_id`, `feedback_id`, 현재 상태값을 함께 보여줌
- 내 피드백 목록 페이지 구현 항목
- 로그인 사용자 기준 `requester_id + requester_tenant_id` 필터 적용 `(완료)`
- 목록 컬럼 초안: 제목, 요청 유형, 작성일, 내부 상태 중심 게시판 형태 구현 `(완료)`
- `workspace` 필터와 상태 필터 제공
- 상단 우측 검색 입력 및 행 전체 클릭 상세 이동 구현 `(완료)`
- 리스트 하단 좌측 작성 페이지 이동 버튼 구현 `(완료)`
- 피드백 상세 페이지 구현 항목
- ABC 원문 본문/첨부/댓글 조회 영역
- 우리 시스템 상태 메타데이터 영역: 현재는 최소 상태 배지와 댓글 입력 영역 중심으로 단순화 `(부분 완료)`
- 승인형 업무는 승인 이력 타임라인 노출
- 일반 Q&A형 업무는 이슈 연결 상태와 답변 상태를 노출
- `workspace`별 채널/필드 자동 선택 로직
- `app_id`, `service_type_id`, 메뉴 코드 -> `workspaceCode` 해석
- `workspaceCode` -> `workspace_channel_mappings`로 ABC Channel 결정
- `workspaceCode` -> `workspace_field_mappings`로 폼 필드와 검증 규칙 로드
- 채널 매핑이 없으면 작성 화면 진입 전 관리자 설정 누락 오류로 처리
- 작성 완료 후 매핑 저장 처리
- 우리 시스템은 ABC API 응답의 `feedback_id`를 수신한 뒤 같은 요청 컨텍스트에서 내부 티켓과 1:1 매핑 저장
- 저장 실패 시 사용자에게는 접수 보류 상태를 안내하고, 운영자에게 재처리 대상 알림을 남김
- 사용자 조회 화면의 상태 노출 원칙
- ABC 상태값이 아니라 우리 시스템 `support_tickets.status_code`를 주 상태값으로 사용함
- 보조 상태로 `approval_status`, `sync_status`, `issue_link_status`를 함께 노출함
- 사용자는 본인 데이터만 조회 가능하고, `tenant_id` 불일치 데이터는 절대 노출하지 않음
- 1차 실무 작업 순서
- 사용자 공통 진입 URL 규칙 확정
- `workspace` 해석 API 또는 미들웨어 구현
- 폼 템플릿 조회 API 구현 `(완료 - 스텁 기준)`
- 작성 API와 목록 API 구현 `(완료 - 현재 secretary-api 연동 기준)`
- 상세 API 구현: 내부 상태 + ABC 원문 조합 응답 `(부분 완료)`
- 샘플 `workspace` 1종으로 작성 -> 목록 -> 상세 흐름 검증 `(완료)`
- 완료 기준
- 최소 1개 `workspace`에서 작성, 목록, 상세 흐름이 연결되어 있어야 함
- 작성 완료 시 `support_tickets``abc_feedback_mappings`가 함께 생성되어야 함
- 현재 확인 범위: ABC `feedbacks` 저장과 관리자 조회까지는 확인 완료, 내부 매핑 저장은 secretary-api 경로 기준 완료 메모를 유지하되 web fallback 경로는 별도 검증 필요
- 목록/상세 화면에서 내부 상태값이 ABC 원문과 함께 노출되어야 함
### 4.6 운영/관리 화면 셋업
- 현재 저장소 기준 확인 결과
- 실제 서비스 웹앱 `apps/web/src/pages` 이하에 `/ops`, `/admin/issues` 라우트가 존재함 `(완료)`
- 운영/관리 화면은 실제 페이지로 1차 이관되었고 승인/이슈 생성 흐름은 현재 secretary-api 및 내부 DB 경로 기준으로 계속 확장 중임 `(부분 완료)`
- 운영 화면 라우팅 구조 초안
- `/ops/approvals`: 승인 대기 목록
- `/ops/tickets`: 운영 전체 목록
- `/ops/tickets/:ticketId`: 운영 상세
- `/admin/issues`: 이슈 생성 대상 목록
- `/admin/issues/:ticketId`: 피드백-이슈 연결 상세
- 승인 대상 조회 및 승인/반려 처리 구현 항목
- 조회 조건: `request_approvals.approval_status = PENDING` 또는 `support_tickets.requires_approval = true and status_code = RECEIVED`
- 승인 화면 컬럼 초안: 제목, 요청 유형, 요청자, 요청일시, 현재 상태, 승인 필요 사유
- 승인 시 처리 순서: 권한 검증 -> `request_approvals` 기록 -> `support_tickets.status_code` 갱신 -> 알림 이벤트 발행
- 반려 시 처리 순서: 반려 사유 저장 -> 사용자 노출 상태 갱신 -> 알림 이벤트 발행
- ABC 관리자 UI 진입 링크 또는 연동 포인트
- 운영 상세에서 대응 ABC 원문 링크와 관리자 UI 바로가기 제공
- 관리자 화면에서는 `abc_feedback_id`, `abc_channel_id`, `abc_feedback_url`을 함께 노출
- 필요 시 ABC 기본 목록/상세 UI를 새 탭으로 열고, 우리 시스템은 상태 제어와 처리 이력만 담당함
- 피드백-이슈 연결 흐름 구현 항목
- 승인 완료 또는 운영 판단 완료 상태의 티켓만 이슈 생성 가능
- 이슈 생성 시 ABC 관리자 API 호출 후 `issue_id`와 연결 시각을 내부 이력에 저장
- 이미 연결된 티켓은 중복 생성이 아니라 기존 이슈 링크로 유도
- 연결 실패 시 내부 상태는 유지하고 `sync_status` 또는 운영 이력에 실패 원인을 저장
- 담당자 배정, 처리 메모, 상태 전이 로직
- 상태 전이 초안: `RECEIVED -> PENDING_APPROVAL -> APPROVED -> IN_PROGRESS -> RESOLVED -> CLOSED`
- 반려 흐름 초안: `PENDING_APPROVAL -> REJECTED`
- 담당자 배정은 `current_assignee_id`, `current_assignee_tenant_id`에 기록
- 처리 메모와 상태 변경 이력은 `operator_histories` 또는 `ticket_activity_logs`에 누적 저장
- 처리 결과/답변 입력 후 사용자 노출 데이터 동기화
- 내부 상태 변경 시 사용자 상세 화면의 상태 배지와 최근 처리 결과를 즉시 반영
- 필요 시 ABC 원문 댓글 또는 이슈 상태와 최소 동기화 규칙을 정의
- 사용자가 보는 최종 상태는 우리 시스템 `support_tickets.status_code`를 기준으로 유지
- 역할별 접근 제한 검증 항목
- 승인자: 승인 대기 목록과 승인/반려만 가능, 전체 운영 설정 변경 불가
- 운영 담당자: 전체 운영 목록, 상태 변경, 담당자 배정, 처리 메모 가능
- 관리자: Project/Channel/Member/이슈 연결 관리 가능
- 일반 사용자: 운영/관리 URL 직접 접근 시 403 처리
- 1차 실무 작업 순서
- 승인 대기 목록 API 구현 `(완료 - 현재 preview 흐름 기준)`
- 승인/반려 API 구현 및 `request_approvals` 저장 `(부분 완료 - 현재 내부 상태 전이 기준)`
- 운영 상세 API 구현: 내부 상태 + ABC 원문 링크 조합
- 담당자 배정 및 처리 메모 API 구현
- 이슈 생성 및 피드백-이슈 연결 API 구현 `(부분 완료 - 현재 secretary-api 연동 기준)`
- 운영/관리 화면을 실제 라우트 구조로 정리 `(완료)`
- 승인 -> 이슈 생성 -> 사용자 상세 반영 시나리오 검증 `(부분 완료)`
- 완료 기준
- 승인자가 승인/반려를 수행하면 내부 승인 이력과 티켓 상태가 함께 갱신되어야 함
- 운영 담당자가 상태 변경과 처리 메모를 남길 수 있어야 함
- 관리자 화면에서 승인 완료 티켓을 이슈 생성 대상으로 식별하고 연결할 수 있어야 함
### 4.7 외부 연동/알림 준비
- 대상 연동 범위 초안
- 메일 알림
- 사내 메신저 또는 협업 도구 알림
- 후속 업무 시스템 연계
- 운영 설정 누락 및 동기화 실패 감지
- 이벤트 기준 초안
- 접수 완료: 요청 제목, 접수 번호, 상세 링크
- 승인 대기: 승인 대상자, 요청 요약, 승인 링크
- 승인 완료: 요청 제목, 승인 결과, 다음 단계, 상세 링크
- 반려 완료: 반려 사유, 재작성 또는 문의 안내
- 처리 완료: 처리 결과 요약, 추가 확인 필요 여부, 상세 링크
- `RESOLVED`: 처리 완료 결과 알림
- 자산/차량 연동형 업무는 승인 완료 후 후속 처리 이벤트를 별도 발행함
- 구현 준비 항목
- 알림 채널별 템플릿 정의
- 이벤트 발생 시점 정의
- 재시도 및 실패 로그 정책 정의
- 민감 정보 마스킹 정책 정의
- 완료 기준
- 승인 완료/처리 완료 이벤트 기준 알림 발송 검증
- Mock 또는 실제 연동 경로가 최소 1개 이상 확인되어야 함
### 4.8 통합 검증/시연 준비
- 시연용 고정 데이터 준비
- 샘플 접수 데이터 3종: 승인형 1건, 일반 Q&A 1건, 처리완료 1건
- 테스트 계정: 일반 사용자 1명, 승인자 1명, 운영 담당자 1명, 관리자 1명
- 체크리스트형 실행 순서
- 일반 사용자 로그인 또는 진입
- 작성 페이지 진입
- 접수 등록
- 목록 확인
- 상세 확인
- 승인자 승인 또는 반려
- 운영 담당자 상태 갱신
- 관리자 이슈 연결 확인
- 사용자 상세 최종 상태 확인
- 실패 케이스 점검
- 권한 없는 사용자 진입 차단
- `workspace` 미매핑 처리
- 세션 만료 후 복귀 흐름
- ABC 또는 내부 DB 저장 실패 시 안내 문구
- 완료 기준
- end-to-end 시연 체크리스트가 역할별로 1회 이상 통과되어야 함
## 5. 우선순위 기준 Task Backlog
- P0
- `workspace`, Project, Channel, Field, 권한 구조 확정
- `support_tickets`, `abc_feedback_mappings`, `request_approvals` 최소 실DB 경로 검증 유지
- 사용자 작성 -> 목록 -> 상세 기본 흐름 안정화
- P1
- 승인/반려, 이슈 연결, 댓글, 상태 반영 흐름 고도화
- 동적 필드, 첨부, 알림, 운영 이력 보강
- P2
- 업무 시스템 연동, 알림 고도화, 확장 업무 테이블, 마이그레이션 추적 체계 보강
- 2, 3은 최종 완료 판단을 위한 선행 조건으로 유지
## 6. 실무용 체크리스트
- 완료: FastAPI 앱 골격, `/api/health`, `/api/workspaces`, `/api/workspaces/{workspaceCode}/form-template`, `POST /api/workspaces/{workspaceCode}/tickets` 스텁, 1차 Alembic 마이그레이션 파일, `apps/web` 사용자 `/support` 라우트, `/ops`, `/admin/issues` 실제 페이지 이관 `(완료)`
| 항목 | 상태 | 메모 |
| --- | --- | --- |
| ABC Web/API 로컬 실행 확인 | 완료 | `start-local.sh`, `check-local.sh` 실행 및 기본 포트/헬스체크 확인 완료 |
| MySQL 스키마 생성 | 진행중 | `apps/secretary-api` Alembic 1차 마이그레이션 파일 생성 완료. 실제 DB 적용 검증은 남아 있음 |
| MySQL 스키마 생성 | 부분 완료 | `baron_support` DB 생성, Alembic 1차/2차 적용, Docker bridge IP 기준 실DB 검증 완료. 로컬 공통 접속 방식 정리는 남아 있음 |
| `support_tickets` 테이블 생성 | 완료 | 실DB 생성 및 `description`, 상태 컬럼, `extra_fields` 포함 티켓 저장/조회 검증 완료 |
| `request_approvals` 테이블 생성 | 부분 완료 | 실DB 생성 확인 완료. 실제 승인 저장 흐름 검증은 다음 단계 |
| `abc_feedback_mappings` 테이블 생성 | 완료 | 실DB 생성 및 티켓 생성 시 매핑 레코드 저장 검증 완료 |
| ABC DB `feedbacks` 저장/조회 | 완료 | `apps/web` fallback 경로 기준으로 `userfeedback.feedbacks` 생성과 관리자 Feedback 화면 조회 검증 완료. 채널 필드 스키마에 맞춰 `contents`/`message` 본문만 저장되도록 정리 완료 |
| 사용자 작성/목록/상세 연결 | 완료 | `apps/web``/support/[workspaceCode]/new|list|[ticketId]|index` 구현 및 작성->목록->상세 흐름 확인 |
| 승인 목록/운영 목록 연결 | 부분 완료 | `/ops`, `/admin/issues` 라우트와 승인/이슈 생성 스텁 흐름 구현. 실DB/실권한 연동은 남아 있음 |
| ABC 이슈 생성/연결 연동 | 부분 완료 | secretary-api 및 `/admin/issues` 경로에 내부 상태 전이/연결 스텁 구현. 실제 ABC API 연동은 남아 있음 |
| 처리 결과 사용자 노출 동기화 | 부분 완료 | 사용자 상세에 상태 배지 및 댓글 UI 노출. 실제 운영 결과 동기화는 남아 있음 |
| end-to-end 시연 검증 | 진행중 | 역할별 시나리오, 고정 데이터, 실패 케이스 체크리스트 초안 반영 |
@@ -0,0 +1,235 @@
# BARON-SSO 연계 Task 정리
## 1. 문서 목적
본 문서는 현재 저장소 기준으로 이미 완료된 작업, 부분 완료 상태인 작업, 앞으로 진행해야 할 작업을 다시 정리한 실행 문서임.
특히 다음 두 문서를 하나의 실행 기준으로 연결하는 목적을 가짐.
- 사용자/운영 흐름 기준: `docs/architecture_secretary_sso_user_scenarios.md`
- 권한/테넌트/분기 기준: `docs/architecture_secretary_sso_role_access.md`
이 문서에서는 기존 초안 중 현재 방향과 맞지 않는 항목은 정리하고, 실제 구현 상태와 다음 작업 순서를 우선으로 기록함.
## 2. 현재 기준선
### 2.1 운영 구조 기준
- 인증 원본은 BARON-SSO 임.
- BARON-SSO는 인증과 현재 테넌트 문맥 확인까지만 담당함.
- 최종 권한과 관리자 여부는 내부 DB에서 판단함.
- 일반 사용자는 `/support/[workspaceCode]/**` 경로로 진입함.
- 관리자는 `/main/project/[projectId]/feedback?channelId=[channelId]` 중심 관리자 콘솔로 진입함.
- 초기 프로젝트 기준은 `EGBIM`, `TOVA`, `GAIA`, `KNGIL`, `Q&A_Platform` 임.
- 초기에는 프로젝트당 기본 채널 1개와 프로젝트 관리자 역할만 운영하고, 이후 필요 시 채널 관리자와 세분화 권한으로 확장함.
- 테스트 단계에서는 일반 사용자의 피드백 작성 페이지 프로젝트명과 workspace 식별자·표시명을 모두 `Q&A_Platform`으로 고정함. URL에서는 `Q%26A_Platform`으로 인코딩함.
- RP별 진입 버튼과 프로젝트/채널 분기는 전체 RP 목록과 매핑이 확정된 이후 별도 작업으로 진행함.
### 2.2 상태 표기 기준
- `완료`: 현재 저장소와 로컬 검증 기준으로 동작 경로가 확인된 작업
- `부분 완료`: 일부 구현 또는 연결은 되었지만, 최종 운영 기준으로는 비어 있는 작업
- `대기`: 설계만 있고 아직 구현 또는 운영 반영이 시작되지 않은 작업
## 3. 완료된 작업 정리
### 3.1 로컬 실행 기반
| 항목 | 상태 | 정리 |
| ---------------------------------- | ---- | ------------------------------------------------------------------------------------- |
| 로컬 ABC/연관 서비스 실행 스크립트 | 완료 | `start-local.sh`, `check-local.sh` 기준 로컬 기동 경로가 정리되어 있음 |
| 기본 개발 저장소 구조 | 완료 | `apps/web`, `apps/api`, `apps/secretary-api`, `apps/e2e` 등 작업 단위가 분리되어 있음 |
| secretary-api 기본 앱 구조 | 완료 | FastAPI 엔트리포인트, health, tickets 라우트, Alembic 구조가 존재함 |
### 3.2 사용자 지원 포털
| 항목 | 상태 | 정리 |
| ------------------------ | ---- | ---------------------------------------------------------------- |
| 사용자 작성 페이지 | 완료 | `/support/[workspaceCode]/new` 구현 완료 |
| 사용자 목록 페이지 | 완료 | `/support/[workspaceCode]/list` 구현 완료 |
| 사용자 상세 페이지 | 완료 | `/support/[workspaceCode]/[ticketId]` 구현 완료 |
| 기본 진입 리다이렉트 | 완료 | `/support/[workspaceCode]` 진입 시 목록 흐름 존재 |
| 폼 템플릿 조회 | 완료 | workspace 기반 작성 폼 템플릿 조회 API 연결 완료 |
| 작성/목록/상세 기본 흐름 | 완료 | 최소 1개 workspace 기준 작성 -> 목록 -> 상세 흐름 구현 완료 |
| 댓글 CRUD | 완료 | 사용자 상세와 관리자 상세 시트에서 댓글 생성/수정/삭제 흐름 존재 |
| 상세 상태 반영 | 완료 | 사용자 상세에서 내부 상태와 ABC 이슈 연결 상태를 함께 반영함 |
### 3.3 secretary-api 및 내부 DB
- 내부 권한 원본 분리 | 진행 | `baron_support.support_users`, `support_roles`, `support_role_assignments`를 추가하고, `user_workspace_access`는 유효 workspace 권한표로 사용
- ABC 권한과 분리 | 진행 | Secretary의 로그인 후 분기와 지원 API는 ABC `users.type`, `roles`, `members`를 조회하지 않음
- 기존 접근 데이터 백필 | 진행 | 기존 `user_workspace_access`를 내부 사용자와 workspace 역할 할당으로 이관
| 항목 | 상태 | 정리 |
| ------------------------- | ---- | ---------------------------------------------------------------------------------- |
| support ticket 기본 API | 완료 | workspaces, tickets, ticket detail, comments, approve, issue 관련 기본 라우트 존재 |
| 핵심 마이그레이션 1차/2차 | 완료 | `support_tickets`, 상태 컬럼, `extra_fields` 등 현재 UI 기준 컬럼 반영 완료 |
| 내부 티켓 저장 | 완료 | `support_tickets` 생성 흐름 구현 완료 |
| ABC 피드백 매핑 저장 | 완료 | 내부 티켓 생성 후 `abc_feedback_mappings` 저장 흐름 구현 완료 |
| 승인/이슈 상태 컬럼 | 완료 | `approval_status`, `sync_status`, `issue_link_status` 관리 구조 존재 |
| 코멘트 저장 구조 | 완료 | `ticket_comments` 및 관련 API 흐름 구현 완료 |
| 첨부 메타데이터 테이블 | 완료 | `attachments` 테이블과 ORM 모델 존재 |
### 3.4 첨부파일 업로드 현재 완료 범위
| 항목 | 상태 | 정리 |
| ------------------------- | ---- | --------------------------------------------------------------------------------- |
| 작성 페이지 파일 선택 UI | 완료 | 작성 화면에서 다중 첨부 선택 가능 |
| multipart 프록시 처리 | 완료 | `apps/web` API route 에서 multipart 파싱 후 secretary-api 로 전달함 |
| secretary-api 업로드 수신 | 완료 | multipart 요청에서 `attachments` 수신 가능 |
| 로컬 파일 저장 | 완료 | 업로드 파일을 로컬 디렉터리에 저장하고 메타데이터를 `attachments` 테이블에 기록함 |
| 파일 크기 제한 | 완료 | 30MB 제한 설정 존재 |
### 3.5 운영 보조 화면
| 항목 | 상태 | 정리 |
| -------------------------- | ---- | -------------------------------------------------------------- |
| `/ops` 페이지 | 완료 | 승인 대기/처리 흐름용 운영 보조 화면 존재 |
| `/admin/issues` 페이지 | 완료 | 이슈 연결 대상 확인용 운영 보조 화면 존재 |
| 관리자 상세 시트 댓글 연계 | 완료 | 관리자 피드백 상세 시트에서 support ticket 댓글 흐름 사용 가능 |
## 4. 부분 완료 작업 정리
### 4.1 role_access 기준 운영 구조 반영
| 항목 | 상태 | 남은 내용 |
| ------------------------ | --------- | -------------------------------------------------------------------------------------- |
| 프로젝트 구조 문서화 | 부분 완료 | 새 기준 프로젝트 목록은 role_access 에 정리됐지만 실제 운영 seed/매핑 반영은 남아 있음 |
| 프로젝트/채널 실제 생성 | 대기 | `EGBIM`, `TOVA`, `GAIA`, `KNGIL`, `Q&A_Platform` 프로젝트와 동일명 기본 채널 생성 필요 |
| 관리자 접근 정책 | 대기 | 프로젝트/채널별 운영자 접근 범위와 내부 권한 테이블 반영 필요 |
| 문의 구분 기반 확장 전략 | 부분 완료 | 문서 초안은 있으나 실제 필드/라우팅/큐 분기 규칙은 미구현 |
### 4.2 BARON-SSO 및 권한 분기
| 항목 | 상태 | 남은 내용 |
| ------------------------------------ | ---- | ------------------------------------------------------------------------------------- |
| BARON-SSO 로그인 연동 | 대기 | 실제 OIDC/OAuth 연동 구현 필요 |
| 세션의 SSO 식별자와 `tenant_id` 처리 | 대기 | 현재 테스트 사용자 상수 기반 흐름을 실제 세션 기반으로 전환해야 함 |
| 내부 사용자 매핑 | 대기 | SSO 식별자와 내부 `users` 또는 `auth_identities` 매핑 구조 미구현 |
| 사용자/관리자 페이지 분기 | 대기 | 로그인 후 내부 DB 권한 조회를 기준으로 `/support/...` 와 관리자 콘솔 자동 분기 미구현 |
| 프로젝트별 세부 권한 제한 | 대기 | 관리자 콘솔 진입 후 프로젝트별 재검증 로직 미구현 |
| 관리자 권한 설정 페이지 | 대기 | 관리자 콘솔 메뉴 내 권한 설정 페이지 추가 필요 |
### 4.3 사용자 지원 포털 보강
| 항목 | 상태 | 남은 내용 |
| --------------------------- | --------- | -------------------------------------------------------------------- |
| 동적 필드 전체 사용 | 부분 완료 | 현재 title/description 중심 최소 렌더링만 사용 중 |
| 문의 구분 필드 반영 | 대기 | role_access 기준 문의 구분 저장 및 운영 큐 분기 연결 필요 |
| 비밀글 기능 | 부분 완료 | `support_tickets.is_secret`, 작성 폼, 작성자/관리자 조회 제한 연결 완료; E2E 및 마이그레이션 검증 필요 |
| 첨부파일 상세 조회/다운로드 | 대기 | 업로드 저장은 되지만 목록/상세 응답과 다운로드 경로는 없음 |
| 첨부파일 ABC 연동 | 대기 | 현재 ABC 생성 시 제목/본문만 전송하고 첨부는 내부 로컬 저장만 수행함 |
| 작성 완료 결과 표준화 | 부분 완료 | 현재 ticket 상태는 보이지만 운영 기준 완료 UX 는 추가 정리 필요 |
### 4.4 운영/관리 기능 보강
| 항목 | 상태 | 남은 내용 |
| ------------------------------ | --------- | --------------------------------------------------------------------------- |
| 승인 이력 정교화 | 부분 완료 | 승인 상태 전이와 기본 API 는 있으나 실제 운영 권한/사유/이력 정책 보강 필요 |
| 이슈 생성/연결 운영 흐름 | 부분 완료 | 상태 동기화와 보조 화면은 있으나 실제 운영 정책/권한 제어는 추가 필요 |
| 담당자 배정/처리 메모 | 대기 | 전담 운영 테이블/화면/이력 흐름 미구현 |
| 관리자 콘솔 프로젝트 단위 제한 | 대기 | role_access 기준 프로젝트별 접근 제한 미구현 |
| 관리자 권한 설정 UI | 대기 | 콘솔 메뉴와 설정 화면에서 프로젝트 관리자 부여/해제 기능 필요 |
### 4.5 데이터 및 운영 자동화
| 항목 | 상태 | 남은 내용 |
| ----------------- | --------- | ------------------------------------------------------------------------------- |
| seed 데이터 정리 | 부분 완료 | 테스트 흐름은 있으나 새 프로젝트 기준 seed 재정리 필요 |
| 공통 권한 테이블 | 대기 | 우선 `user + project + role` 조합 저장 구조 구체화 필요 |
| 알림/후속 연계 | 대기 | 승인 완료, 처리 완료, 설정 누락 알림 등 운영 이벤트 미구현 |
| E2E 시나리오 고정 | 부분 완료 | 화면 시연은 가능하나 role_access 기준 사용자/관리자 분기 시나리오 정리는 부족함 |
## 5. 앞으로 해야 할 작업
### 5.1 P0: role_access 기준 운영 구조 확정
- BARON-SSO에서 받을 사용자 식별 claim 확정
- 내부 사용자 매핑 키 확정
- 프로젝트 `EGBIM`, `TOVA`, `GAIA`, `KNGIL`, `Q&A_Platform` 실제 생성
- 각 프로젝트 기본 채널 1개 생성
- 사용자 Q&A 이동 URL과 `workspaceCode` 매핑 표 확정
- 테스트 단계에서는 모든 일반 사용자 작성 화면의 프로젝트명과 workspace 표시명을 `Q&A_Platform`으로 고정
- 프로젝트별 관리자 접근 정책 확정
### 5.2 P0: 로그인 후 분기와 권한 처리 구현
- BARON-SSO 로그인 연동 구현
- 세션에서 SSO 식별자와 `tenant_id` 를 읽는 공통 계층 추가
- 내부 사용자 및 프로젝트 관리자 권한 조회 계층 추가
- 일반 사용자 -> `/support/[workspaceCode]/new` 이동 구현
- 관리자 -> 관리자 콘솔 기본 진입 경로 이동 구현
- 관리자 콘솔 진입 후 프로젝트별 재검증 구현
- 권한 설정 페이지를 관리자 콘솔 메뉴에 추가
### 5.3 P1: 사용자 포털 기능 마감
- 문의 구분 필드 추가 및 저장
- 비밀글 필드 추가 및 저장 `(부분 완료 - support_tickets.is_secret 및 사용자 폼 연결)`
- 비밀글 조회 권한 및 마스킹 정책 구현 `(작성자/관리자 제한 적용, E2E 검증 필요)`
- `workspace`별 동적 필드 전체 렌더링 정리
- 첨부파일 응답 스키마 추가
- 사용자 상세 첨부 목록 및 다운로드 구현
- 필요 시 첨부 ABC 저장 전략 확정 후 브릿지 구현
- 업로드 실패/부분 저장 실패 시 사용자 안내 문구 표준화
### 5.4 P1: 운영/관리 기능 마감
- 승인/반려 사유와 승인 이력 화면 정리
- 담당자 배정, 처리 메모, 상태 변경 이력 구현
- 이슈 생성 후 사용자 상세 상태 반영 규칙 정리
- 관리자 콘솔에서 프로젝트/문의구분 기반 큐 분리
- 운영 보조 화면 `/ops`, `/admin/issues` 와 실제 관리자 콘솔 역할 분담 정리
- 관리자 권한 설정 화면에서 프로젝트 관리자 관리 기능 구현
### 5.5 P2: 운영 안정화 및 검증
- role_access 기준 테스트 계정 3종 이상 준비
- 일반 사용자/담당자/시스템 관리자 시나리오별 E2E 체크리스트 작성
- 프로젝트 미매핑, 권한 부족, 세션 만료 예외 처리 검증
- 비밀글 작성자, 관리자, 권한 없는 사용자 시나리오 검증
- 알림 및 운영 설정 누락 감지 체계 추가
- 문서 간 용어 통일: tenant, workspace, project, channel, 문의 구분
## 6. 바로 실행할 다음 작업 제안
### 6.1 1차 묶음
- 테스트용 기본 프로젝트명 `Q&A_Platform` 고정 흐름 검증
- RP별 진입 버튼 -> `workspaceCode` -> ABC `project_id/channel_id` 매핑표 확정
- SSO 식별자 -> 내부 사용자 매핑 규칙 확정
- 로그인 후 사용자/관리자 분기 미들웨어 또는 라우터 초안 작성
### 6.2 2차 묶음
- 비밀글 저장/조회 제한 API 추가 `(부분 완료 - 생성/응답/작성자·관리자 조회 제한)`
- 첨부파일 조회/다운로드 API 추가
- 사용자 상세 첨부 표시 추가
- 문의 구분 필드 저장 및 관리자 큐 표시 초안 추가
### 6.3 3차 묶음
- 프로젝트별 관리자 접근 제어 테이블 설계
- 운영 보조 화면과 실제 관리자 콘솔 권한 경계 정리
- role_access 기준 E2E 시나리오 문서화
## 7. 이번 정리에서 제거한 구버전 가정
- `INTRANET_SUPPORT`, `SOFTWARE_QA` 중심 Project 초안은 현재 우선 기준에서 제외함.
- 기존 task 문서에 있던 인트라넷 신청형 업무 중심 Channel 목록은 role_access 기준 Q&A 프로젝트 구조가 확정될 때까지 보조 아이디어로만 취급함.
- 테스트 상수 사용자 기준 흐름은 임시 검증 수단으로 유지하되, 운영 기준 완료 항목으로 보지 않음.
## 8. 최종 요약
- 현재 구현은 사용자 지원 포털, 내부 티켓 저장, 댓글, 일부 운영 보조 화면까지는 갖춰져 있음.
- 새 기준선은 BARON-SSO 인증 연동, 내부 DB 권한 모델, 프로젝트 관리자 중심 운영 구조, 관리자 권한 설정 페이지, 비밀글 기능임.
- 지금 가장 큰 공백은 SSO 실연동, 로그인 후 내부 DB 권한 분기, 프로젝트별 접근 제어, 비밀글, 첨부 조회/다운로드, 문의 구분 기반 운영 큐 분리임.
- 이후 작업은 SSO에서 신원만 받고, 권한과 화면 제어는 내부 DB 기준으로 반영하는 순서로 진행해야 함.
## 15. 테스트 단계 프로젝트 표시 및 RP 분기 보류 기준
- 현재 테스트 단계의 일반 사용자 피드백 작성 페이지는 프로젝트명과 workspace 식별자·표시명을 모두 `Q&A_Platform`으로 고정 표시함. URL에서는 `Q%26A_Platform`으로 인코딩함.
- 현재 로그인 후 기본 진입은 테스트용 단일 작성 흐름을 검증하기 위한 임시 동작으로 취급함.
- RP별 진입 버튼은 각 RP 식별자 또는 `workspaceCode`를 전달하고, 서버에서 ABC `project_id``channel_id`로 매핑하는 구조를 최종 기준으로 삼음.
- 전체 RP 목록, 프로젝트명, 기본 채널, API Key, 권한 범위가 확정되기 전까지 RP별 작성 페이지 자동 분기는 구현하지 않음.
- RP 분기 구현 시 URL의 프로젝트명만 신뢰하지 않고, 서버 측 매핑과 사용자 workspace 접근 권한을 함께 검증함.
@@ -0,0 +1,502 @@
# 사내 지원 플랫폼 사용자별 사용 시나리오
## 1. 문서 목적
본 문서는 [architecture_secretary_sso_components_v2.md](./architecture_secretary_sso_components_v2.md)에서 정의한 통합 아키텍처를 바탕으로, 사용자 유형별 실제 사용 시나리오를 정리한 문서임.
핵심 목적은 다음과 같음.
- 어떤 사용자가 어떤 경로로 진입하는지 명확히 구분함.
- BARON-SSO, 우리 시스템, ABC User Feedback가 각각 어느 시점에 개입하는지 흐름으로 표현함.
- 조회 결과가 보이는 지점과 처리 결과가 저장되는 지점을 시나리오별로 시각화함.
## 2. 사용자 유형 정의
| 사용자 유형 | 주요 진입 경로 | 주요 목적 | 로그인 후 주 분기 화면 |
| --- | --- | --- | --- |
| 인트라넷 일반 사용자 | 인트라넷 포털 또는 공통 업무 진입점 | 물품 신청, 도서 신청, 차량 신청, 사내 문의 등록 | 사용자 피드백 작성 페이지, 내 피드백 목록/상세, ABC 조회 화면 |
| S/W 프로그램 사용자 | 각 S/W 프로그램 메뉴 또는 공통 업무 진입점 | 앱 전용 Q&A 등록, 장애/문의 접수 | 사용자 피드백 작성 페이지, 내 문의 목록/상세, ABC 조회 화면 |
| 승인자 | 인트라넷 또는 운영 포털의 공통 진입점 | 승인 대기 건 검토, 승인/반려 처리 | 승인 대상 목록, 피드백 상세, ABC 관리자 UI |
| 운영 담당자 | 운영 메뉴 또는 공통 진입점 | 전체 티켓 관리, 상태 변경, 후속 조치, 공지/알림 | 운영 목록/상세, 이슈 관리, ABC 관리자 UI |
## 3. 사용 기술 스택
| 영역 | 사용 기술 | 역할 |
| --- | --- | --- |
| 인증/사용자 식별 | BARON-SSO, OAuth 2.0, OIDC | 사용자 로그인, `user_id`, `tenant_id`, 토큰 발급 |
| 사용자/운영 화면 | 사용자 피드백 작성 페이지, ABC User Feedback Web Frontend | 사용자 입력, 목록 조회, 상세 확인, 이슈 관리, 관리자 설정 |
| 제어 백엔드 | FastAPI | `workspace` 매핑, 권한 분기, 승인 워크플로우, 외부 연동 orchestration |
| 제어 데이터 저장소 | 자체 DB (예: MySQL) | `support_tickets`, `request_approvals`, 매핑, 운영 이력 저장 |
| 원본 피드백 저장소 | ABC User Feedback | 피드백 원문, 첨부, 채널, 필드, 이슈 관리 |
| 외부 알림 연동 | Naver Works API, SMS Gateway | 승인/반려/처리 결과 알림 |
| 업무 시스템 연동 | 자산 API, 차량 API, 조직도 API 등 | 후속 처리 자동화 및 업무 데이터 조회 |
기술 스택의 책임 분리는 다음과 같음.
- BARON-SSO는 인증과 조직 식별의 기준 시스템임.
- 사용자와 승인자, 운영 담당자는 동일한 인증 상태에서 접속하고, 로그인 후에는 역할과 업무 컨텍스트에 따라 화면이 분기됨.
- 사용자 피드백 작성 페이지는 일반 사용자의 직접 입력 경험을 담당함.
- ABC User Feedback Web Frontend는 원문 조회와 운영 담당자용 관리 화면을 담당함.
- FastAPI는 ABC와 자체 DB 사이에서 정책과 절차를 제어하는 핵심 백엔드임.
- ABC User Feedback는 게시글 원본과 운영용 채널/이슈 관리 기능을 담당함.
## 4. 사용자별 시나리오 맵
```mermaid
flowchart LR
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.5px,color:#0f172a;
classDef control fill:#f8fafc,stroke:#334155,stroke-width:1.2px,color:#111827;
classDef external fill:#fff7ed,stroke:#c2410c,stroke-width:1.2px,color:#111827;
U1[인트라넷 일반 사용자]
U2[S/W 프로그램 사용자]
U3[승인자]
U4[운영 담당자]
ENTRY[공통 업무 진입점\n포털 / 앱 / 운영 메뉴]:::result
SSO[BARON-SSO]:::external
G1[우리 시스템\n세션 확인 / 역할 판정 / workspace 매핑]:::control
UI1[사용자 화면 분기
작성 / 내 목록 / 내 상세]
:::result
UI2[운영 화면 분기
승인 목록 / 운영 목록 / 상세]
:::result
UI3[ABC User Feedback 관리자 UI]:::result
APP[우리 시스템\n권한/워크플로우/매핑]:::control
ABC[ABC User Feedback\n원본 저장소]:::result
U1 -->|공통 진입| ENTRY
U2 -->|공통 진입| ENTRY
U3 -->|공통 진입| ENTRY
U4 -->|공통 진입| ENTRY
ENTRY -->|로그인 세션 확인 또는 SSO 요청| SSO
SSO -->|user_id, tenant_id, role context 반환| G1
G1 -->|일반 사용자 + 신청/문의 컨텍스트| UI1
G1 -->|승인자/운영자 + 운영 컨텍스트| UI2
UI2 -->|필요 시 ABC 관리자 기능 진입| UI3
UI1 -->|등록/조회 요청| APP
UI2 -->|승인/운영 요청| APP
UI3 -->|원문/이슈 관리 요청| APP
APP -->|원본 데이터 저장/조회| ABC
APP -->|상태/승인/권한 기록| APP
```
## 5. 시나리오 1: 인트라넷 일반 사용자의 신청 등록
### 5.1 시나리오 설명
인트라넷 일반 사용자는 물품 신청, 도서 신청, 차량 신청, 사내 문의와 같은 업무를 등록하는 주체임. 이 사용자는 우리 시스템이 직접 구현한 사용자 피드백 작성 페이지에서 신청 내용을 입력하고, 우리 시스템은 그 뒤의 승인 정책과 처리 상태를 제어하며 원문은 ABC에 저장함.
### 5.2 주요 단계
1. 사용자가 인트라넷 포털에서 특정 지원 서비스를 선택함.
2. 사용자 피드백 작성 페이지가 BARON-SSO 인증을 수행하고 `user_id`, `tenant_id`를 확보함.
3. 우리 시스템이 진입한 서비스 코드를 `workspace`로 매핑하고, 해당 사용자를 적절한 ABC 프로젝트/채널로 연결함.
4. 사용자는 우리 시스템이 직접 구현한 입력 화면에서 신청 내용을 작성함.
5. 사용자가 신청서를 제출하면 ABC가 원본 피드백 데이터를 저장함.
6. 우리 시스템은 생성된 `feedback_id`를 내부 티켓과 매핑하고 상태, 승인 필요 여부, 운영 메타데이터를 저장함.
7. 사용자와 담당자는 ABC UI에서 원문을 보고, 우리 시스템은 상태와 승인 결과를 동기화함.
### 5.3 Flow
```mermaid
flowchart LR
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.5px,color:#0f172a;
classDef control fill:#f8fafc,stroke:#334155,stroke-width:1.2px,color:#111827;
classDef external fill:#fff7ed,stroke:#c2410c,stroke-width:1.2px,color:#111827;
U[인트라넷 일반 사용자]
P[인트라넷 포털]
SSO[BARON-SSO]:::external
UI[사용자 피드백 작성 페이지
우리 시스템]:::result
M1[우리 시스템\nworkspace 매핑 / 접근 제어]:::control
M3[우리 시스템\n티켓 메타데이터 저장]:::control
A1[ABC Feedback 저장 처리]:::control
A2[ABC 원본 데이터 저장소]:::result
D1[우리 DB\nsupport_tickets / approvals / mappings]:::result
R1[ABC 접수 결과 화면\n내 신청 내역]:::result
U -->|지원 서비스 선택| P
P -->|신청 화면 호출| UI
UI -->|SSO 인증 요청| SSO
SSO -->|user_id, tenant_id 반환| UI
UI -->|서비스 코드 전달| M1
M1 -->|workspace 기반 프로젝트/채널 연결| UI
UI -->|입력 화면 작성 및 제출| A1
A1 -->|본문/필드값 저장| A2
A2 -->|feedback_id 생성 이벤트 전달| M3
M3 -->|상태/승인여부/매핑 기록| D1
A2 -->|원문 조회 제공| R1
A2 -->|원본 접수 데이터 확보| R1
```
## 6. 시나리오 2: S/W 프로그램 사용자의 Q&A 등록
### 6.1 시나리오 설명
S/W 프로그램 사용자는 특정 앱 내부의 도움말 또는 지원 메뉴를 통해 진입하며, 본인이 사용 중인 앱에 대응하는 전용 사용자 피드백 작성 페이지로 연결됨. 우리 시스템은 이를 적절한 ABC Q&A 채널에 매핑하여 원문을 저장함.
### 6.2 주요 단계
1. 사용자가 특정 S/W 프로그램 내 지원 메뉴를 클릭함.
2. 앱이 `app_id` 또는 서비스 식별자를 포함한 상태로 사용자 피드백 작성 페이지 진입 URL을 호출함.
3. BARON-SSO 인증 후 우리 시스템이 해당 식별자를 `workspace`와 ABC 채널로 매핑함.
4. 사용자는 우리 시스템의 사용자 피드백 작성 페이지에서 앱 전용 문의 폼을 작성함.
5. 사용자가 문의를 등록하면 ABC에 원본 피드백이 저장됨.
6. 우리 시스템은 내부 티켓과 앱-채널 매핑 정보를 함께 저장함.
7. 사용자는 필요 시 ABC 조회 화면에서 본인 문의 원문을 확인하고, 우리 시스템은 처리 상태를 별도 메타데이터로 관리함.
### 6.3 Flow
```mermaid
flowchart LR
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.5px,color:#0f172a;
classDef control fill:#f8fafc,stroke:#334155,stroke-width:1.2px,color:#111827;
classDef external fill:#fff7ed,stroke:#c2410c,stroke-width:1.2px,color:#111827;
U[S/W 프로그램 사용자]
APP0[각 S/W 프로그램]
SSO[BARON-SSO]:::external
UI[사용자 피드백 작성 페이지
우리 시스템]:::result
B1[우리 시스템\napp_id -> workspace 매핑]:::control
B2[우리 시스템\n권한/채널 판정]:::control
A1[ABC 채널/필드 구성]:::result
A2[ABC Feedback 저장 처리]:::control
A3[ABC 원본 Q&A 저장소]:::result
D1[우리 DB\nworkspace_channel_mappings\nsupport_tickets]:::result
R1[ABC 문의 등록 결과\n내 문의 목록]:::result
U -->|지원 메뉴 클릭| APP0
APP0 -->|app_id 포함 화면 호출| UI
UI -->|SSO 인증 요청| SSO
SSO -->|사용자 식별 정보 반환| UI
UI -->|app_id 전달| B1
B1 -->|workspace 및 채널 결정| B2
B2 -->|작성 페이지용 채널 매핑 전달| UI
B2 -->|채널 필드 정의 참조| A1
UI -->|문의 등록 제출| A2
A2 -->|Q&A 원본 저장| A3
B2 -->|내부 티켓 및 매핑 기록| D1
A3 -->|문의 원본 표시| R1
D1 -->|처리 상태 표시| R1
```
## 7. 시나리오 3: 승인자의 승인/반려 처리
### 7.1 시나리오 설명
승인자는 일반 사용자와 동일한 로그인 상태에서 접속한 뒤, 역할과 업무 컨텍스트에 따라 승인 화면으로 분기되어 원문과 이슈를 확인하면서 우리 시스템이 제어하는 승인 정책에 따라 승인 여부를 결정하는 역할임. 승인 결과는 상태 변화와 알림 발송으로 이어짐.
### 7.2 주요 단계
1. 승인자가 공통 업무 진입점에서 승인 업무로 접속함.
2. 로그인 세션 확인 또는 BARON-SSO 인증 후 우리 시스템이 승인 권한과 ABC 프로젝트 접근 권한을 검증 및 동기화함.
3. 승인자는 역할에 따라 분기된 운영 화면 또는 ABC 관리자 UI에서 승인 대상 피드백과 연결 이슈를 조회함.
4. 승인자가 승인 또는 반려 판단을 수행하면 우리 시스템이 해당 결과를 내부 승인 로직에 반영함.
5. 우리 시스템이 `request_approvals`와 티켓 상태를 갱신함.
6. 필요 시 메신저 또는 SMS 알림을 발송함.
7. 사용자와 운영자는 분기된 조회 화면과 ABC UI, 내부 상태 동기화 결과를 기준으로 처리 결과를 확인함.
### 7.3 Flow
```mermaid
flowchart LR
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.5px,color:#0f172a;
classDef control fill:#f8fafc,stroke:#334155,stroke-width:1.2px,color:#111827;
classDef external fill:#fff7ed,stroke:#c2410c,stroke-width:1.2px,color:#111827;
U[승인자]
UI[승인 화면 분기\n우리 시스템 운영 화면 / ABC 관리자 UI]:::result
SSO[BARON-SSO]:::external
C1[우리 시스템\n승인 권한 판정 / 멤버 동기화]:::control
C2[ABC 피드백 / 이슈 조회]:::control
C3[우리 시스템\n승인/반려 처리]:::control
D1[우리 DB\nrequest_approvals / support_tickets]:::result
X1[Naver Works / SMS]:::external
R1[ABC 결과 화면\n처리 상태]:::result
U -->|공통 진입 후 승인 업무 선택| UI
UI -->|세션 확인 또는 SSO 인증 요청| SSO
SSO -->|user_id, role context 반환| UI
UI -->|승인 대상 조회 요청| C1
C1 -->|권한 확인 후 피드백/이슈 조회| C2
C2 -->|승인 대상 데이터 반환| UI
UI -->|승인/반려 판단 수행| C3
C3 -->|승인 이력 및 상태 갱신| D1
C3 -->|결과 알림 발송 요청| X1
D1 -->|처리 결과 반영| R1
```
## 8. 시나리오 4: 운영 담당자의 후속 조치 및 이슈 관리
### 8.1 시나리오 설명
운영 담당자는 일반 사용자와 동일한 로그인 상태에서 접속한 뒤, 역할과 업무 컨텍스트에 따라 운영 화면으로 분기되어 접수 건을 실제 처리 단계로 연결하는 역할을 담당함. 예를 들어 자산 배정, 차량 배차, 원격지원, Q&A 이슈화, 상태 종료 처리 등을 수행함.
### 8.2 주요 단계
1. 운영 담당자가 공통 업무 진입점에서 운영 업무로 접속함.
2. 로그인 세션 확인 또는 BARON-SSO 인증 후 우리 시스템이 운영 권한과 조회 가능한 `workspace` 범위를 판정함.
3. 분기된 운영 화면 또는 ABC 관리자 UI에서 전체 피드백 또는 특정 `workspace`에 대응되는 채널을 조회함.
4. 우리 시스템이 상태, 우선순위, 요청 유형, 담당자 기준 메타데이터를 ABC 데이터와 동기화함.
5. 운영 담당자가 특정 피드백을 열어 원본 데이터와 승인 이력을 함께 확인함.
6. 필요 시 ABC 원문에 대응되는 이슈를 생성하거나 연결함.
7. 우리 시스템이 자산/차량/원격지원/알림 등의 확장 프로세스를 수행함.
8. 처리 완료 후 내부 상태를 종료하거나 추가 조치 필요 상태로 갱신하고, 필요한 결과를 ABC에 반영함.
9. 사용자와 운영자는 분기된 조회 화면과 ABC UI를 기준으로 최신 원문과 처리 상태를 확인함.
### 8.3 Flow
```mermaid
flowchart LR
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.5px,color:#0f172a;
classDef control fill:#f8fafc,stroke:#334155,stroke-width:1.2px,color:#111827;
classDef external fill:#fff7ed,stroke:#c2410c,stroke-width:1.2px,color:#111827;
U[운영 담당자]
UI[운영 화면 분기\n우리 시스템 운영 화면 / ABC 관리자 UI]:::result
C1[우리 시스템\n목록 필터/상태 조회]:::control
C2[우리 시스템\n후속 조치 엔진]:::control
C3[우리 시스템\n이슈/알림/자산 연계]:::control
D1[우리 DB\n티켓/승인/운영 이력]:::result
A1[ABC 원본 데이터]:::result
A2[ABC 이슈/피드백 연결]:::control
X1[자산/차량/외부 API]:::external
R1[ABC 운영 결과 화면\n최신 상태]:::result
U -->|공통 진입 후 운영 업무 선택| UI
UI -->|조건별 검색 요청| C1
C1 -->|티켓/상태/담당자 조회| D1
D1 -->|목록 및 상세 데이터 반환| UI
UI -->|원문 확인 요청| A1
UI -->|후속 조치 실행| C2
C2 -->|이슈 연결 또는 상태 갱신| A2
C2 -->|운영 이력 저장| D1
C3 -->|자산/차량/알림 API 호출| X1
D1 -->|최신 결과 집계| R1
A2 -->|원본 연계 상태 반영| R1
```
## 9. 권한별 핵심 차이
| 구분 | 일반 사용자 | S/W 사용자 | 승인자 | 운영 담당자 |
| --- | --- | --- | --- | --- |
| 인증 | BARON-SSO | BARON-SSO | BARON-SSO | BARON-SSO |
| 진입 기준 | 서비스 메뉴 | 앱 메뉴 | 승인 업무 메뉴 | 운영 메뉴 |
| 주 작업 | 신청 등록/조회 | 문의 등록/조회 | 승인/반려 | 상태 관리/후속 조치 |
| ABC 직접 사용 여부 | 부분 사용 | 부분 사용 | 직접 사용 | 직접 사용 |
| 우리 시스템 의존도 | 높음 | 높음 | 높음 | 높음 |
| 핵심 저장 위치 | ABC + 우리 DB | ABC + 우리 DB | ABC + 우리 DB | ABC + 우리 DB |
## 10. 문서 활용 가이드
- 화면 설계 시에는 일반 사용자용 작성 화면은 우리 시스템에서 직접 구현하고, 운영/관리 화면은 ABC 기본 UI를 우선 활용함.
- API 설계 시에는 어떤 단계가 우리 시스템 API인지, 어떤 단계가 ABC 연동인지 분리해서 정의함.
- 권한 설계 시에는 `workspace`, `role`, `approval permission`, `operator permission`을 별도 축으로 설계함.
- 운영 정책 수립 시에는 승인자와 운영 담당자의 역할이 섞이지 않도록 본 문서의 시나리오를 기준으로 책임 범위를 정리함.
## 11. 시나리오 기준 ABC User Feedback 활용 기능 및 API
### 11.1 활용 원칙
- 최종 사용자 인증은 BARON-SSO를 기준으로 하고, ABC의 사용자 인증 체계는 운영용 또는 관리용 보조 수단으로만 사용함.
- 일반 사용자는 우리 시스템의 사용자 피드백 작성 페이지를 사용하고, 운영 담당자는 ABC User Feedback UI를 직접 사용함.
- 우리 시스템은 사용자 입력 화면, ABC UI 진입 제어, 권한 동기화, 상태/승인 메타데이터 관리에 집중함.
- 따라서 ABC API는 크게 `프로젝트/채널 사전 구성`, `원본 피드백 저장/조회`, `이슈 관리`, `멤버/역할 동기화`, `운영 자동화` 용도로 사용함.
### 11.2 시나리오별 ABC 활용 기능
| 시나리오 | ABC에서 활용할 기능 | 실제 활용 API | 비고 |
| --- | --- | --- | --- |
| 인트라넷 일반 사용자 신청 등록 | 신청 원문 저장 | `POST /api/projects/:projectId/channels/:channelId/feedbacks` | 우리 작성 페이지에서 API 호출로 ABC 원문 저장 |
| 인트라넷 일반 사용자 신청 등록 | 이미지 포함 신청 저장 | `POST /api/projects/:projectId/channels/:channelId/feedbacks-with-images` | 첨부 업로드 자동화가 필요한 경우 사용 |
| S/W 프로그램 사용자 Q&A 등록 | 앱 전용 문의 원문 저장 | `POST /api/projects/:projectId/channels/:channelId/feedbacks` | 우리 작성 페이지에서 `workspace`와 채널 매핑 후 API 호출 |
| 승인자 승인/반려 | 원문 참조용 피드백 목록 조회 | `POST /api/admin/projects/:projectId/channels/:channelId/feedbacks/search` | ABC 관리자 UI와 병행 사용 |
| 운영 담당자 후속 조치 | 이슈 생성 | `POST /api/admin/projects/:projectId/issues` | 문의를 이슈로 승격할 때 사용 |
| 운영 담당자 후속 조치 | 피드백-이슈 연결 | `POST /api/admin/projects/:projectId/channels/:channelId/feedbacks/:feedbackId/issue/:issueId` | 문의와 처리 이슈 연결 |
| 운영 담당자 후속 조치 | 이슈 목록 조회 | `POST /api/admin/projects/:projectId/issues/search` | 현황판, 대시보드 구성에 사용 |
| 운영 담당자 후속 조치 | 이슈 상세 조회 | `GET /api/admin/projects/:projectId/issues/:issueId` | 연결된 피드백 수, 상태 확인 |
| 운영 담당자 후속 조치 | 피드백 수정 | `PUT /api/admin/projects/:projectId/channels/:channelId/feedbacks/:feedbackId` | 원문 보정 또는 메타 필드 갱신 |
| 운영 담당자 후속 조치 | 피드백 내보내기 | `POST /api/admin/projects/:projectId/channels/:channelId/feedbacks/export` | 운영 보고용 다운로드 |
| 운영 환경 준비 | 프로젝트 생성/조회 | `POST /api/admin/projects`, `GET /api/admin/projects` | 서비스 단위 프로젝트 생성 |
| 운영 환경 준비 | 채널 생성/조회 | `POST /api/admin/projects/:projectId/channels/`, `GET /api/admin/projects/:projectId/channels/` | 서비스별 접수 채널 구성 |
| 운영 환경 준비 | 채널 필드 관리 | `PUT /api/admin/projects/:projectId/channels/:channelId/fields` | 신청서 항목과 채널 필드 정합 |
| 운영 권한 구성 | 프로젝트 멤버 관리 | `POST /api/admin/projects/:projectId/members`, `POST /api/admin/projects/:projectId/members/search` | 운영자/담당자 접근 권한 부여 |
| 운영 권한 구성 | 역할 관리 | `GET /api/admin/projects/:projectId/roles/`, `POST /api/admin/projects/:projectId/roles/` | 프로젝트별 역할 정의 |
일반 사용자와 운영 담당자의 일상 사용 흐름은 ABC UI를 우선 사용하고, 위 API는 다음 목적에서 활용함.
- 초기 프로젝트/채널/역할 셋업 자동화
- BARON-SSO 사용자와 ABC 멤버 구조 동기화
- 내부 상태값과 ABC 피드백/이슈 연계 자동화
- 대량 등록, 이관, 배치 처리, 외부 시스템 연동
### 11.3 ABC에서 주로 재사용할 기능 묶음
| 기능 묶음 | 재사용 목적 | 설명 |
| --- | --- | --- |
| Feedback 저장 기능 | 문의/신청 원문 저장 | 제목, 본문, 필드값, 이미지 등 원본 데이터 보존 |
| Channel/Field 관리 기능 | 서비스별 입력 스키마 구성 | 채널별 신청서 항목과 커스텀 필드 유지 |
| Issue 관리 기능 | 운영 후속 조치 기록 | 피드백을 운영 이슈와 연결하고 상태 추적 |
| Project/Member/Role 관리 기능 | 운영 구조 설정 | 프로젝트, 채널, 담당자, 권한 체계 운영 |
| Export 기능 | 운영 보고/분석 | 피드백 데이터 다운로드 및 외부 분석 활용 |
## 12. 우리가 직접 구현해야 하는 부분
### 12.1 구현 원칙
- ABC는 원본 데이터와 관리자 기능을 담당하고, 실제 서비스 경험은 우리 시스템이 완성함.
- 일반 사용자가 사용하는 사용자 피드백 작성 페이지는 우리 시스템에서 직접 구현하고, 운영/관리 기능은 ABC UI를 최대한 활용함.
- 따라서 인증, 권한, 워크플로우, 업무 상태, 외부 시스템 연계뿐 아니라 일반 사용자 입력 화면도 직접 구현 범위에 포함됨.
### 12.2 직접 구현 범위 요약
| 구현 영역 | 우리가 직접 구현할 내용 | ABC만으로 부족한 이유 |
| --- | --- | --- |
| SSO 연동 | BARON-SSO 로그인, 토큰 검증, `user_id`, `tenant_id` 세션 처리 | ABC 인증은 우리 조직의 통합 인증 기준이 아님 |
| 진입 경로 해석 | `app_id`, `service_type_id`, 메뉴 코드 등을 `workspace`로 매핑 | ABC는 외부 서비스 진입 맥락을 이해하지 못함 |
| 사용자 피드백 작성 화면 | 일반 사용자용 입력 폼, 유효성 검사, 제출 완료 UX, ABC 저장 API 호출 | ABC 기본 화면만으로는 서비스별 사용자 경험과 진입 컨텍스트를 충분히 통제하기 어려움 |
| ABC 진입 제어 | 사용자를 올바른 프로젝트/채널/화면으로 연결하는 리다이렉트 및 딥링크 로직 | ABC는 외부 포털 메뉴 체계와 직접 연결되지 않음 |
| 채널/필드 표준화 | 서비스별 필드 템플릿, 채널 생성 규칙, 운영 정책 자동화 | ABC만으로는 사내 표준 신청서 체계를 일관되게 강제하기 어려움 |
| 내부 티켓 모델 | `support_tickets` 기반 상태, 우선순위, 업무 유형, 담당자 관리 | ABC 피드백 원문만으로 운영 제어 메타데이터를 관리하기 부족함 |
| 승인 워크플로우 | `request_approvals`, 승인선, 승인/반려 이력, 다단계 결재 | 사내 결재 정책은 ABC 기본 기능 범위를 넘음 |
| 권한 동기화 | 사용자별 조회 범위, 승인 권한, 운영 권한, 워크스페이스별 접근 제어를 ABC 멤버/역할과 연동 | 프로젝트/멤버 권한만으로 세밀한 업무 분기 어려움 |
| 원본-내부 매핑 | ABC `feedback_id`와 내부 티켓 ID, 기존 시스템 ID 매핑 | 이관 및 통합 운영을 위한 별도 식별자 체계 필요 |
| 외부 연동 | 자산, 차량, 조직도, 메신저, SMS, 기타 사내 API 연계 | ABC는 사내 업무 시스템 orchestration 역할이 아님 |
| 알림 정책 | 상태 변경별 알림 템플릿, 채널 선택, 재발송 규칙 | 사내 정책 기반 알림 제어가 필요함 |
| 집계/리포팅 | 업무 유형별 KPI, 승인 지연, 처리 SLA, 부서별 통계 | ABC 통계는 일반 피드백 관점이라 업무형 리포트에 한계가 있음 |
| 마이그레이션 도구 | 기존 게시글, 첨부, 댓글, 분류 정보 이관 및 검증 | 기존 시스템 식별자와의 추적 관리가 필요함 |
### 12.3 시나리오별 구현 책임 정리
| 시나리오 | ABC가 담당하는 부분 | 우리가 직접 구현하는 부분 |
| --- | --- | --- |
| 인트라넷 일반 사용자 신청 등록 | 피드백 원문 저장, 첨부 저장 | SSO 로그인, 사용자 피드백 작성 페이지 구현, 서비스 진입 제어, 채널 연결, 신청 상태 저장, 승인선 생성 |
| S/W 프로그램 사용자 Q&A 등록 | 앱 전용 채널에 문의 원문 저장, 목록/상세 UI 제공 | 사용자 피드백 작성 페이지 구현, 앱 식별자 해석, `workspace` 매핑, 사용자 권한 판정, ABC 진입 URL 제어, 상태 메타데이터 관리 |
| 승인자 승인/반려 | 피드백/이슈 조회 UI 제공 | 승인 규칙, 승인 이력, 결과 알림, 상태 전이, ABC 멤버 권한 동기화 |
| 운영 담당자 후속 조치 | 이슈 생성, 피드백-이슈 연결, 원문 검색/수정 UI 제공 | 담당자 배정, 자산/차량 후속 처리, SLA 추적, 내부 상태 관리, 외부 API 실행 |
| 운영 환경 준비 | 프로젝트/채널/필드/멤버/역할 관리 API와 관리자 UI | 서비스 구조 설계, 채널 정책 표준화, 권한 모델링, 셋업 자동화 |
### 12.4 구현 우선순위 제안
1. `BARON-SSO 연동 + 사용자 피드백 작성 페이지 + workspace 매핑 + ABC 진입 제어`를 먼저 구현해야 함.
2. 그 다음 `support_tickets + approvals + abc_feedback_mappings` 중심의 내부 제어 DB를 구축해야 함.
3. 이후 `ABC 멤버/역할 동기화 + 승인 워크플로우 + 외부 연동`을 확장하는 순서가 가장 현실적임.
4. 마지막으로 `마이그레이션 도구 + 통계/리포트 + 셋업 자동화`를 붙여 운영 전환을 마무리하는 구성이 적절함.
## 13. 사용자 작성부터 이슈 생성 및 처리, 답변 입력까지의 시스템 데이터 처리 모식도
### 13.1 목적
이 절은 일반 사용자가 사용자 피드백 작성 페이지에서 문의를 등록한 뒤, 담당자가 이슈를 생성하고 처리한 후 답변 또는 처리 결과를 입력할 때까지 데이터가 어느 시스템에 저장되고 어떻게 연결되는지를 한 번에 보여주기 위한 보조 모식도임.
핵심 확인 포인트는 다음과 같음.
- 일반 사용자는 보통 BARON-SSO 인증을 마치고, 우리 시스템이 접근 권한과 연결 채널을 판정한 뒤 작성 페이지에 진입함.
- 운영 담당자와 관리자도 동일하게 BARON-SSO 인증 또는 세션 확인을 거친 뒤, 역할에 맞는 운영 화면 또는 관리자 화면으로 분기 진입함.
- 사용자 입력 원문은 ABC User Feedback에 저장됨.
- 제어용 상태와 승인/운영 메타데이터는 우리 DB에 저장됨.
- 담당자 이슈 생성과 처리 이력은 ABC 이슈와 우리 DB가 함께 관리함.
- 최종 답변 또는 처리 결과는 사용자에게 보이는 원문/상태 화면으로 다시 동기화됨.
### 13.2 단계별 데이터 처리 요약
| 단계 | 수행 주체 | 주요 처리 | 주요 저장 위치 |
| --- | --- | --- | --- |
| 1 | 일반 사용자 | 신청/문의 메뉴로 진입하고 보호된 작성 페이지 접근을 시도 | 진입 전 상태는 포털 또는 앱 컨텍스트 |
| 2 | BARON-SSO + 우리 시스템 | 로그인 세션 확인 또는 SSO 인증 수행, `user_id`, `tenant_id` 확보 | 우리 시스템 세션 / 인증 컨텍스트 |
| 3 | 우리 시스템 | `workspace`, 채널, 권한, 요청 유형을 판정하고 작성 가능한 대상인지 검증 | 우리 시스템 제어 로직 |
| 4 | 일반 사용자 | 권한이 확인된 작성 페이지에서 문의/신청 내용을 작성하고 제출 | 제출 전 일시 상태는 우리 시스템 화면 메모리 |
| 5 | 우리 시스템 | ABC 저장 API 호출 | 우리 시스템 제어 로직 |
| 6 | ABC User Feedback | 피드백 원문, 필드값, 첨부, 생성된 `feedback_id` 저장 | ABC Feedback 원본 저장소 |
| 7 | 우리 시스템 | `feedback_id`를 받아 내부 티켓, 승인 필요 여부, 상태값 생성 | 우리 DB `support_tickets`, `abc_feedback_mappings` |
| 8 | 운영 담당자/관리자 | 공통 업무 진입점 또는 운영 메뉴로 접속하고 보호된 운영 화면 접근을 시도 | 운영 포털 또는 운영 메뉴 컨텍스트 |
| 9 | BARON-SSO + 우리 시스템 | 로그인 세션 확인 또는 SSO 인증 수행 후 운영 권한, 관리자 권한, 조회 가능한 `workspace` 범위 판정 | 우리 시스템 세션 / 권한 컨텍스트 |
| 10 | 승인자/담당자/관리자 | 역할에 따라 분기된 운영 화면 또는 ABC 관리자 UI에서 승인, 조회, 이슈 작업 수행 | 우리 시스템 운영 화면 + ABC 관리자 UI |
| 11 | 승인자/담당자 | 승인 또는 반려 판단 | 우리 DB `request_approvals`, `support_tickets` |
| 12 | 담당자/관리자 | ABC 관리자 UI에서 이슈 생성 및 피드백-이슈 연결 | ABC Issue, ABC Feedback-Issue 연결 정보 |
| 13 | 우리 시스템 | 담당자 배정, 처리 메모, 외부 연동 결과, SLA 상태 저장 | 우리 DB `support_tickets`, 운영 이력, 알림 이력 |
| 14 | 담당자/관리자 | 처리 결과 또는 답변 입력, 필요 시 원문/상태 반영 | ABC 원문 표시 정보 + 우리 DB 상태값 |
| 15 | 일반 사용자 | ABC 조회 화면 또는 우리 시스템 조회 화면에서 처리 결과 확인 | ABC 원문 + 우리 DB 상태 동기화 결과 |
### 13.3 End-to-End 데이터 흐름 모식도
```mermaid
flowchart LR
classDef result fill:#eef6ff,stroke:#1d4ed8,stroke-width:1.5px,color:#0f172a;
classDef control fill:#f8fafc,stroke:#334155,stroke-width:1.2px,color:#111827;
classDef external fill:#fff7ed,stroke:#c2410c,stroke-width:1.2px,color:#111827;
U[일반 사용자]
OP[운영 담당자]
AD[관리자/승인자]
E1[신청/문의 메뉴 진입\n포털 또는 앱]:::result
E2[운영/관리 메뉴 진입\n운영 포털 또는 관리자 메뉴]:::result
A0[BARON-SSO 인증 / 세션 확인]:::external
G0[우리 시스템\n사용자/운영자/관리자 권한 판정\nworkspace / 채널 / 역할 결정]:::control
W[사용자 화면 분기\n작성 / 내 목록 / 내 상세]:::result
A1[ABC Feedback 저장 API]:::control
A2[ABC 원본 피드백 저장소\n본문/필드값/첨부/feedback_id]:::result
D1[우리 DB\nsupport_tickets\nabc_feedback_mappings]:::result
O0[운영 화면 분기\n운영 목록 / 승인 목록 / 상세]:::result
AP[승인 처리 엔진\n승인/반려/상태 전이]:::control
D2[우리 DB\nrequest_approvals\nstatus history]:::result
O1[ABC 관리자 UI]:::result
I1[ABC Issue 저장소\nissue / feedback-issue link]:::result
O2[우리 시스템\n담당자 배정/처리 메모/외부 연동]:::control
D3[우리 DB\n운영 이력 / 알림 / SLA / 후속조치]:::result
X1[자산/차량/Naver Works/SMS]:::external
A3[결과 반영 화면\nABC 원문 + 우리 시스템 상태 동기화]:::result
R1[최종 사용자 조회 화면]:::result
U -->|신청/문의 메뉴 선택| E1
OP -->|운영 업무 진입| E2
AD -->|관리/승인 업무 진입| E2
E1 -->|로그인 또는 기존 세션 확인| A0
E2 -->|로그인 또는 기존 세션 확인| A0
A0 -->|user_id, tenant_id, role context 확보| G0
G0 -->|일반 사용자 화면 진입| W
W -->|작성 완료 후 제출| A1
A1 -->|피드백 원문 저장| A2
A2 -->|feedback_id 반환 및 내부 매핑 시작| D1
D1 -->|운영/관리 대상 건 생성| O0
G0 -->|운영자/관리자 화면 진입| O0
O0 -->|승인 필요 건은 승인 엔진으로 전달| AP
AP -->|승인/반려 이력 기록| D2
D2 -->|승인 결과 반영| D1
O0 -->|원문 확인 및 관리자 기능 진입| O1
O1 -->|이슈 생성/연결 요청| I1
I1 -->|이슈 식별자/연결 정보 반환| O2
O2 -->|담당자 배정, 처리 메모, 상태 갱신| D3
O2 -->|support_tickets 상태 갱신| D1
O2 -->|외부 업무 시스템 호출| X1
O1 -->|답변/처리 결과 입력| O2
D1 -->|최종 상태 동기화| A3
D3 -->|처리 결과 동기화| A3
O2 -->|답변/처리 결과 반영| A3
A3 -->|내 목록 / 상세 / 결과 조회 제공| R1
```
### 13.4 저장 책임 정리
| 데이터 유형 | 우선 저장 위치 | 설명 |
| --- | --- | --- |
| 사용자 입력 본문, 필드값, 첨부 | ABC User Feedback | 원문 데이터의 기준 저장소 |
| `feedback_id`와 내부 티켓 연결 | 우리 DB | `support_tickets`, `abc_feedback_mappings`에 저장 |
| 승인 상태, 반려 이력, 단계별 결재 결과 | 우리 DB | `request_approvals`, 상태 이력 테이블에 저장 |
| 운영 이슈, 피드백-이슈 연결 | ABC User Feedback | 운영 추적의 기준 이슈 저장소 |
| 담당자 배정, 처리 메모, SLA, 외부 연동 결과 | 우리 DB | 운영 제어와 리포팅을 위한 내부 메타데이터 |
| 사용자에게 보여줄 최종 처리 결과 | ABC 원문 조회 화면 + 우리 DB 동기화 결과 | 원문과 상태를 함께 보여주는 최종 표시 데이터 |
### 13.5 설계 해석 포인트
- 사용자 작성은 인증과 접근 권한 검증, `workspace`-채널 판정이 끝난 뒤 시작되며, 작성 UX와 진입 제어는 우리 시스템이 담당하지만 원문 자체는 ABC에 남김.
- 승인과 운영 처리 단계에서는 우리 DB가 상태 제어의 기준 시스템 역할을 수행함.
- 담당자의 이슈 생성은 ABC를 기준으로 하되, 그 이슈를 어떤 업무 상태로 해석할지는 우리 시스템이 담당함.
- 답변 입력 또는 처리 결과 입력은 단순 텍스트 등록이 아니라 `support_tickets` 상태, 승인 결과, 운영 메모, 외부 연동 결과를 함께 묶어 사용자에게 노출하는 흐름으로 봐야 함.
@@ -0,0 +1,313 @@
# EGBIM QA 데이터 스테이징 이관 작업 목록
> 기준 원본: `scripts/egbim_qa.sql`
> 대상 환경: 스테이징의 `baron_support` 및 필요한 경우 `userfeedback`
> 상태: 설계·준비 단계
> 원칙: 이 문서는 작업 목록이며, 아직 DB 이관을 실행하지 않는다.
## 1. 이관 범위
`egbim_qa.sql`은 EGBIM 기존 QA 시스템의 게시글, 댓글, 게시글 첨부파일, 댓글 이미지 데이터 덤프다. 원본 SQL을 스테이징의 `baron_support`에 그대로 실행하지 않고, Secretary의 표준 티켓·댓글·첨부 구조로 변환한다.
원본 덤프에서 확인된 주요 테이블과 데이터 규모는 다음과 같다.
| 원본 테이블 | 용도 | 덤프 기준 규모 | 주요 관계 |
| --- | --- | ---: | --- |
| `qa_posts` | QA 게시글 | 427건 | 게시글의 루트 레코드 |
| `qa_comments` | 게시글 댓글 | 677건 | `post_id -> qa_posts.post_id` |
| `qa_attachments` | 게시글 첨부파일 | 194건 | `post_id -> qa_posts.post_id` |
| `qa_comment_images` | 댓글 이미지·썸네일 | 42건 | `comment_id -> qa_comments.comment_id` |
실제 실행 전에는 SQL을 파싱하는 사전 점검 스크립트로 행 수와 고아 관계를 다시 계산한다. 위 숫자는 이관 계획 수립용 기준값이다.
## 2. 대상 구조 및 기본 원칙
### 2.1 대상 시스템
- BARON-SSO: 사용자 식별자와 테넌트의 원본
- `baron_support`: workspace, 사용자 접근권한, 표준 티켓, 댓글, 첨부, 외부 시스템 매핑의 원본
- `userfeedback`: ABC User Feedback의 게시글·댓글·첨부 원본 및 콘솔 기능 저장소
이번 EGBIM 이관에서는 권한을 `userfeedback.users.type`, `userfeedback.roles`, `userfeedback.members`에 새로 만들지 않는다. 권한과 사용자 연결은 `baron_support``support_users`, `support_roles`, `support_role_assignments`, `user_workspace_access` 정책을 따른다.
### 2.2 절대 금지 사항
- `egbim_qa.sql` 전체를 `baron_support`에 그대로 import하지 않는다.
- EGBIM의 숫자 ID를 대상 테이블의 ID로 재사용하지 않는다.
- `login_id`, 이메일, 전화번호를 BARON-SSO `sso_subject`로 임의 사용하지 않는다.
- 원본의 절대 파일 경로를 그대로 웹 URL이나 `storage_key`로 노출하지 않는다.
- 기존 스테이징 데이터와 파일을 백업하지 않은 상태에서 본 이관을 실행하지 않는다.
## 3. Workspace와 ABC 매핑 선행 작업
기존 설계 문서의 초기 운영 구조에 맞춰 EGBIM은 다음 workspace로 연결한다.
| 항목 | 계획값 | 확정 필요 |
| --- | --- | --- |
| Secretary workspace code | `EGBIM` | 예 |
| Secretary workspace name | `EGBIM` | 예 |
| Secretary 기본 채널 | `EGBIM` | 예 |
| ABC project | EGBIM 프로젝트 | 실제 `project_id` 확인 |
| ABC channel | EGBIM 기본 채널 | 실제 `channel_id` 확인 |
| 원본 시스템 코드 | `EGBIM_QA` | 예 |
작업 순서:
- [ ] 스테이징 `baron_support.workspaces`에서 `EGBIM` workspace 존재 여부 확인
- [ ] EGBIM workspace의 기본 채널 및 ABC `project_id/channel_id` 확인
- [ ] `workspace_channel_mappings`에 workspace·ABC 프로젝트·채널 매핑 저장
- [ ] 기존 ABC에 이미 이관된 EGBIM 데이터가 있는지 제목, 생성일, 원본 ID 메타데이터로 중복 확인
- [ ] 기존 ABC 데이터와 중복되는 경우 `abc_feedback_mappings`를 우선 연결하고 재생성하지 않을 기준 확정
## 4. 원본-대상 매핑
### 4.1 게시글
| EGBIM 원본 | Secretary 대상 | 매핑 규칙 |
| --- | --- | --- |
| `qa_posts.post_id` | `migration_mappings.source_entity_id` | 원본 게시글 ID로 보존 |
| `qa_posts` 1행 | `support_tickets` 1행 | `source_system = EGBIM_QA` |
| `login_id` 또는 확인된 SSO subject | `support_tickets.requester_id` | identity resolver 결과만 사용 |
| 원본 테넌트 | `requester_tenant_id` | EGBIM SSO tenant 확정 후 저장 |
| `user_id` | `requester_contact` 또는 `extra_fields` | 숫자/레거시 사용자 키로 보존 |
| `user_name` | `requester_name` | 없으면 SSO profile 조회 결과로 보완 |
| `phone` | `requester_phone_number` | 개인정보 접근·마스킹 정책 확인 |
| `department` | `requester_department` | 원본 값 보존 |
| `title` | `title` | 길이 255자 초과 시 원본을 `extra_fields`에 보존 |
| `content` | `description` | HTML/줄바꿈 변환 규칙을 적용 |
| `category` | `category_code` 또는 `extra_fields.category` | 표준 코드가 없으면 원본값 보존 후 매핑 |
| `is_secret` 또는 `is_private` | `support_tickets.is_secret` | 둘 중 하나라도 true면 비밀글; ABC 이관본의 `additional_data.is_secret`에도 원본 플래그 보존 |
| `is_internal` | `requires_approval`가 아님 | 내부 공개범위 필드로 별도 보존; 승인 여부와 혼동 금지 |
| `created_at`, `updated_at` | 동일 대상 시간 필드 | 타임존을 명시하여 변환 |
`attachment`, `complete_form`, `is_read_admin`, `company`, `family_company`, `position` 등 Secretary에 직접 대응하지 않는 값은 버리지 않고 `support_tickets.extra_fields`에 원본 필드명으로 보존한다.
### 4.2 댓글
| EGBIM 원본 | Secretary 대상 | 매핑 규칙 |
| --- | --- | --- |
| `qa_comments.comment_id` | `migration_mappings.source_entity_id` | 댓글 ID로 보존 |
| `qa_comments.post_id` | `ticket_comments.ticket_id` | 게시글 매핑을 먼저 조회 |
| `commenter` 또는 확인된 SSO subject | `author_id` | identity resolver 결과만 사용 |
| 원본 테넌트 | `author_tenant_id` | 게시글/사용자 기준으로 확정 |
| `user_name` | `author_name` | 없으면 SSO profile로 보완 |
| `content` | `content` | 원문 줄바꿈 및 HTML 보존 정책 적용 |
| `created_at`, `updated_at` | 동일 대상 시간 필드 | 원본 시간 보존 |
| 댓글 이미지 존재 여부 | `attachments.comment_id` 연결 | 댓글 생성 후 이미지 이관 |
EGBIM의 `qa_comments.commenter`는 BARON-SSO 기본 로그인 이메일이 아니라 EGBIM에서 사용하던 보조 이메일 ID일 수 있다. 따라서 댓글 이관 시 아래 규칙을 적용한다.
- [ ] `commenter`를 원본 식별자(alias)로 보존하고, 대상 `author_id`에는 BARON-SSO의 `sso_subject`만 저장한다.
- [ ] 보조 이메일 alias를 BARON-SSO `profile.secondary_emails`와 대조하여 기본 로그인 이메일(`profile.email`), subject, tenant, 이름, 부서, 전화번호를 함께 해석한다.
- [ ] 관리자 후보 목록과 일치하고 해당 사용자의 BARON-SSO 계정이 확인되면 `ticket_comments.is_internal = true`, `comment_type = 'ADMIN'`으로 저장한다.
- [ ] 일반 사용자 댓글은 `is_internal = false`, `comment_type = 'COMMENT'`으로 저장한다.
- [ ] 보조 이메일과 SSO 계정이 매핑되지 않으면 관리자 권한을 추정하지 않고 `UNRESOLVED_IDENTITY` 예외 목록에 기록한다.
- [ ] `user_name`은 표시용 원본값으로 우선 보존하되, 검증된 SSO profile 이름이 있으면 별도 매핑 필드로 함께 저장한다.
현재 확정된 EGBIM 관리자 후보 보조 이메일은 다음과 같다.
```text
cjy627@hanmaceng.co.kr,b23072@hanmaceng.co.kr,kjy0426@hanmaceng.co.kr,b21367@hanmaceng.co.kr,rmsgud1202@hanmaceng.co.kr
```
이 목록은 과거 EGBIM 댓글의 관리자 여부를 판정하기 위한 원본 alias 목록이다. 실제 현재 관리자 권한은 `baron_support.support_role_assignments`에 별도로 등록된 역할을 기준으로 하며, 이 목록만으로 새 관리자 권한을 자동 부여하지 않는다.
### 4.3 첨부파일
| EGBIM 원본 | Secretary 대상 | 매핑 규칙 |
| --- | --- | --- |
| `qa_attachments` | `attachments` | `ticket_id` 연결, `comment_id = NULL` |
| `qa_attachments.post_id` | 게시글 매핑의 대상 ticket ID | 게시글 매핑 필수 |
| `ori_name` | `original_file_name` | 사용자 표시용 원본명 |
| `save_path` | `storage_key` 변환 입력 | 절대 경로를 정규화한 뒤 복사 |
| `file_size` | `file_size` | 0 또는 NULL이면 실제 파일 크기로 재계산 |
| `uploaded_at` | `created_at` | 원본 업로드 시간 보존 |
### 4.4 댓글 이미지와 썸네일
`qa_comment_images`는 다음 규칙으로 `attachments`에 적재한다.
- [ ] `comment_id``ticket_comments` 대상 ID를 조회한다.
- [ ] 원본 이미지 파일을 `attachments.storage_key`에 연결한다.
- [ ] `file_name``original_file_name`으로 저장하고 MIME type·확장자를 실제 파일에서 확인한다.
- [ ] `file_size = 0`인 레코드는 파일의 실제 크기를 다시 계산한다.
- [ ] `file_path``thumb_path`의 파일 존재 여부를 각각 검사한다.
- [ ] 대상 모델에는 현재 `thumb_path` 컬럼이 없으므로, 기본안은 원본 이미지를 저장하고 대상 서비스에서 썸네일을 재생성하는 방식으로 한다.
- [ ] 원본 썸네일을 반드시 보존해야 하면 `attachments`에 썸네일 storage key를 추가하는 별도 Alembic migration을 먼저 설계한다.
원본 경로에는 `/egbim/uploads/comment/...`, `/uploads/comment/...` 형식이 섞여 있으므로 문자열만 일괄 치환하지 않는다. 실제 EGBIM 서버의 업로드 루트를 확보하고 파일별 정규화·복사·검증을 수행한다.
### 4.5 관리자 댓글·보조 이메일 매핑
EGBIM 화면에서 사용하는 로그인 ID와 플랫폼 로그인 이메일이 다를 수 있다. EGBIM의 관리자 계정은 BARON-SSO 프로필에서 다음처럼 해석한다.
| 구분 | 값/출처 | 대상 저장 |
| --- | --- | --- |
| EGBIM 관리자 식별자 | `profile.secondary_emails`에 등록된 보조 이메일 | 원본 alias 및 이관 감사 정보 |
| 플랫폼 로그인 ID | BARON-SSO `profile.email` | 사용자 표시·로그인 계정 |
| 사용자 고유 식별자 | BARON-SSO `sub` | `author_id`, `support_users.sso_subject` |
| 테넌트 | BARON-SSO `tenant_id` | `author_tenant_id` |
| 관리자 권한 | `baron_support.support_role_assignments` | 현재 운영 권한 |
| 관리자 댓글 분류 | 관리자 alias와 댓글 `commenter` 일치 | `is_internal=true`, `comment_type=ADMIN` |
Gitea의 `ADMIN_CANDIDATE_EMAILS`에는 아래 값을 쉼표로 등록한다.
```text
cjy627@hanmaceng.co.kr,b23072@hanmaceng.co.kr,kjy0426@hanmaceng.co.kr,b21367@hanmaceng.co.kr,rmsgud1202@hanmaceng.co.kr
```
이 변수의 값은 EGBIM 관리자 보조 이메일 alias 목록이며, BARON-SSO 기본 로그인 이메일 목록이 아니다. 실제 이관 전에 각 alias가 어떤 SSO subject·기본 이메일·tenant로 해석되는지 매핑 CSV를 생성하고, 미매핑 alias는 이관을 중단하거나 승인된 예외로 분리한다. 관리자 등록은 운영자가 관리자 권한 설정 화면에서 `baron_support`에 직접 수행한다.
## 5. 사용자·권한·식별자 처리
EGBIM SQL에는 게시글·댓글 작성자 정보가 `login_id`, `user_id`, `user_name`, 이메일성 값 등 레거시 필드로 들어 있다. 대상 권한은 BARON-SSO의 `sso_subject``tenant_id`를 기준으로 해야 한다.
작업 목록:
- [ ] EGBIM 사용자 식별자와 BARON-SSO profile의 `sso_subject` 매핑 파일 확보
- [ ] BARON-SSO `profile`/`email` scope를 확인하고 `secondary_emails`를 포함한 profile 조회 결과를 확보
- [ ] `EGBIM secondary email alias -> sso_subject, tenant_id, 기본 email, name, department, phone` 매핑 파일 생성
- [ ] 사용자별 `tenant_id`, 이메일, 이름, 전화번호, 부서 조회
- [ ] 매핑되지 않는 작성자는 임시 사용자로 자동 승격하지 않고 `UNRESOLVED_IDENTITY` 상태로 분리
- [ ] `support_users`에 신규 사용자 생성이 필요한지, 기존 SSO 로그인 시 자동 동기화로 충분한지 결정
- [ ] EGBIM workspace의 기본 역할을 `END_USER`로 설정
- [ ] 관리자·프로젝트 관리자는 운영자가 BARON-SSO 기본 이메일로 확인한 뒤 `baron_support.support_role_assignments`에 별도 등록
- [ ] 관리자 후보 alias와 `qa_comments.commenter`가 일치하는 댓글을 `is_internal=true`, `comment_type=ADMIN`으로 변환
- [ ] 이관 데이터의 작성자와 댓글 작성자가 현재 로그인 사용자로 잘못 치환되지 않는지 검증
## 6. 상태·공개범위 매핑
원본 게시글 상태는 `new`, `review`, `deep`, `patch`, `done`이다. 현재 Secretary 표준 상태 코드와 일대일 대응하지 않으므로 아래를 초기 제안으로 사용하되, 실행 전 `support_status_codes`와 운영 화면 표시명을 확정한다.
| EGBIM 상태 | Secretary 제안 | 비고 |
| --- | --- | --- |
| `new` | `RECEIVED` | 접수 상태 |
| `review` | `IN_REVIEW` | 대상 상태 코드 존재 여부 확인 |
| `deep` | `DETAILED_REVIEW` | 정밀검토중 |
| `patch` | `IN_PROGRESS` | 수정 진행 단계; 원본 상태는 `extra_fields` 보존 |
| `done` | `RESOLVED` | 종료/해결 상태 |
- [ ] `IN_REVIEW``DETAILED_REVIEW`의 표시명(검토중·정밀검토중)과 순서를 확인
- [ ] `deep``DETAILED_REVIEW`, `patch``IN_PROGRESS`로 분리하고 원본 상태를 `extra_fields.legacy_status`에 저장
- [ ] EGBIM `category``error/improvement/general`을 각각 `ERROR_QNA/IMPROVEMENT_QNA/GENERAL_QNA`로 변환하고 화면에는 오류문의·개선문의·일반문의로 표시
- [ ] `is_read_admin`은 상태값으로 변환하지 않고 운영용 레거시 플래그로 보존
- [ ] `is_secret`, `is_private`, `is_internal`의 우선순위와 조회 권한 테스트
- [ ] `support_ticket_secrets.sql` 실행 후 작성자/관리자/무권한 사용자별 비밀글 조회 테스트
## 7. 마이그레이션 추적 및 멱등성
기존 V2 설계의 `migration_batches`, `migration_mappings`, `abc_feedback_mappings`를 활용한다.
### 7.1 필수 매핑 종류
| `source_entity_type` | 원본 ID | 대상 |
| --- | --- | --- |
| `POST` | `qa_posts.post_id` | `support_tickets.id`, 선택적으로 ABC feedback ID |
| `COMMENT` | `qa_comments.comment_id` | `ticket_comments.id` |
| `POST_ATTACHMENT` | `qa_attachments.id` | `attachments.id` |
| `COMMENT_IMAGE` | `qa_comment_images.id` | `attachments.id` + comment ID |
각 매핑은 `source_system = EGBIM_QA`와 함께 유일해야 한다. 재실행 시 이미 `SYNCED`인 원본은 건너뛰고, `FAILED`만 오류 원인과 함께 재처리한다.
### 7.2 배치 단계
1. `PRECHECK`: 원본 행 수, 파일 존재, 사용자 식별자, 상태 코드, 중복 여부를 검사한다.
2. `WORKSPACE_READY`: EGBIM workspace/channel/ABC mapping을 확인한다.
3. `IDENTITY_READY`: 작성자와 댓글 작성자의 SSO 매핑을 확정한다.
4. `TICKETS_IMPORTED`: 게시글을 `support_tickets`로 적재한다.
5. `COMMENTS_IMPORTED`: 게시글 매핑을 이용해 댓글을 적재한다.
6. `FILES_IMPORTED`: 게시글 첨부와 댓글 이미지를 복사하고 `attachments`를 적재한다.
7. `ABC_LINKED`: 기존 ABC 레코드 연결 또는 신규 ABC 생성 결과를 기록한다.
8. `VALIDATED`: 건수·관계·권한·파일 열람 검증을 통과한다.
ABC 레코드를 신규 생성할지는 별도 결정한다. 기존 ABC에 이미 같은 데이터가 있다면 중복 생성하지 않고 `abc_feedback_mappings`를 연결하는 것을 우선한다.
## 8. 파일 이관 작업
- [ ] EGBIM 원본 서버에서 게시글 첨부와 댓글 이미지의 실제 업로드 디렉터리 확보
- [ ] `save_path`, `file_path`, `thumb_path`가 가리키는 파일의 존재 여부 수집
- [ ] 파일별 SHA-256, MIME type, 실제 크기 계산
- [ ] 대상 스테이징 업로드 루트와 보존 정책 확인
- [ ] 파일명 충돌 방지를 위해 대상 `storage_key`를 UUID 기반으로 생성
- [ ] `original_file_name`은 표시용으로만 사용하고 경로에는 사용하지 않음
- [ ] 경로 탈출(`..`), 절대 경로, 심볼릭 링크를 차단
- [ ] 복사 후 원본-대상 checksum과 브라우저 다운로드/썸네일 표시를 샘플 검증
- [ ] 파일 복사 실패는 티켓·댓글 이관 실패와 분리해 재처리 가능하게 기록
현재 스테이징 Secretary API는 컨테이너 내부 업로드 루트를 `/app/uploads`로 사용한다. 실제 호스트 경로와 EGBIM 전용 하위 디렉터리는 배포 설정 확인 후 확정한다.
## 9. ABC User Feedback 연계 결정
V2 설계상 ABC는 원본 게시글 저장소이고 Secretary는 권한·업무·연동 제어 계층이다. 따라서 다음 두 가지 중 하나를 실행 전에 선택한다.
### 안 A: ABC와 Secretary 동시 이관
- 게시글마다 ABC feedback을 생성한다.
- 반환된 ABC feedback ID를 `abc_feedback_mappings`에 저장한다.
- 댓글·첨부도 ABC API 지원 범위에 맞춰 후속 생성한다.
- 중복과 API 실패 보상 처리가 필요하다.
### 안 B: Secretary 우선 이관 후 ABC 연결
- 우선 `baron_support`에 원본과 첨부를 이관한다.
- 기존 ABC 레코드가 확인되는 건만 mapping한다.
- 신규 ABC 생성은 별도 동기화 배치로 실행한다.
- 초기 데이터 보존과 재검증에는 더 안전하다.
초기 스테이징 검증은 안 B를 권장한다. 데이터 관계와 권한을 먼저 검증한 뒤, ABC 중복 여부를 확인하고 신규 ABC 생성을 진행한다.
## 10. 실행 전 백업 및 드라이런
- [ ] 스테이징 `baron_support` 전체 백업
- [ ] 스테이징 `userfeedback` 전체 백업 또는 ABC 대상 테이블 백업
- [ ] 현재 `/home/user/baron_qa/uploads` 파일 목록 및 용량 백업
- [ ] `egbim_qa.sql` 원본 해시 기록
- [ ] 대상 workspace/channel/상태 코드/사용자 매핑의 사전 검증
- [ ] 실제 INSERT 없이 변환 결과와 오류 목록만 생성하는 드라이런 실행
- [ ] 드라이런 결과의 expected count와 실제 count 비교
- [ ] 실패 건만 재실행하는 멱등성 테스트
운영 명령은 별도 이관 스크립트가 확정된 뒤 작성한다. 현재는 SQL 파일을 직접 `mysql < egbim_qa.sql` 형태로 실행하지 않는다.
## 11. 검증 및 완료 기준
- [ ] 게시글 427건, 댓글 677건, 게시글 첨부 194건, 댓글 이미지 42건의 실제 원본 건수 재확인
- [ ] 모든 게시글이 유효한 `support_tickets`와 매핑됨
- [ ] 모든 댓글이 올바른 ticket에 연결되고 고아 댓글이 없음
- [ ] 모든 게시글 첨부가 올바른 ticket에 연결됨
- [ ] 모든 댓글 이미지가 올바른 comment와 ticket에 연결됨
- [ ] 작성자·댓글 작성자의 SSO subject/tenant 매핑 누락 목록이 0건이거나 승인된 예외 목록과 일치함
- [ ] 관리자 후보 alias와 일치하는 댓글 수 및 관리자 댓글 ID 목록이 예상 결과와 일치함
- [ ] 관리자 후보가 아닌 댓글이 관리자 댓글로 분류되지 않음
- [ ] 상태별 건수가 원본과 일치하고 `legacy_status`가 보존됨
- [ ] 비밀글·내부글 공개 범위가 일반 사용자에게 노출되지 않음
- [ ] 관리자 콘솔에서 EGBIM workspace의 목록·상세·댓글·첨부를 조회 가능
- [ ] 일반 사용자는 본인에게 허용된 범위의 피드백만 조회 가능
- [ ] 원본 파일과 댓글 이미지 썸네일이 브라우저에서 정상 표시됨
- [ ] ABC 연결 대상의 링크와 ID가 `abc_feedback_mappings`에서 조회됨
- [ ] 배치 재실행 시 중복 ticket/comment/attachment가 생성되지 않음
- [ ] 실패·고아·미매핑 파일 보고서가 생성됨
## 12. 실행 전 확정이 필요한 항목
1. EGBIM 원본 업로드 파일이 실제로 위치한 서버 디렉터리
2. 스테이징의 EGBIM workspace ID, 기본 channel ID, ABC project/channel ID
3. EGBIM 사용자 식별자와 BARON-SSO `sso_subject` 매핑 제공 방식
4. `profile.secondary_emails` 조회 권한 및 보조 이메일 alias 매핑 결과
5. Gitea `ADMIN_CANDIDATE_EMAILS`에 등록할 관리자 alias 5개와 실제 관리자 role assignment 대상
6. 관리자 댓글의 `is_internal/comment_type` 변환 및 일반 사용자 댓글의 공개 범위
7. `review` 상태를 위한 `IN_REVIEW`, `deep` 상태를 위한 `DETAILED_REVIEW` 표준 코드 확인
8. `qa_comment_images.thumb_path`를 대상에서 보존할지 재생성할지
9. ABC 레코드를 신규 생성할지, 기존 ABC만 연결할지
10. 개인정보 필드(phone, company, department)의 보존·마스킹 정책
11. 이관 중 원본 EGBIM을 읽기 전용으로 전환할 시점과 최종 증분 이관 여부
## 13. 완료 후 산출물
- 이관 배치 ID 및 실행 로그
- 원본-대상 ID 매핑 CSV/DB 조회 결과
- 사용자 식별자 미매핑 보고서
- 첨부파일 누락·checksum 불일치 보고서
- 상태·공개범위 변환 결과 보고서
- ABC 중복/연결/생성 결과 보고서
- 롤백용 백업 위치와 복구 절차
@@ -0,0 +1,230 @@
# 다중 프로젝트 관리자 통합 대시보드 설계
## 1. 목적
한 명의 관리자가 여러 프로젝트의 관리자로 지정된 경우, 로그인 직후 각 프로젝트의 주요 현황을 한 화면에서 확인할 수 있는 통합 대시보드를 제공한다.
기존 프로젝트별 대시보드는 유지하고, 여러 프로젝트를 한 화면에서 확인하고 처리하기 위한 전역 관리자 작업공간을 추가한다. 이 화면은 특정 프로젝트에 종속되지 않으며 로그인 후 홈 버튼으로 언제든 접근할 수 있다.
## 2. 기본 진입 규칙
로그인 완료 후 `/main`에서 현재 사용자가 관리할 수 있는 프로젝트 수를 확인한다.
| 조건 | 로그인 후 기본 화면 |
| --- | --- |
| 관리 프로젝트 0개 | 프로젝트 없음 안내 화면 |
| 관리 프로젝트 1개 | 기존 프로젝트 대시보드 |
| 관리 프로젝트 2개 이상 | 통합 관리자 대시보드 |
| SUPER 관리자 | 전체 프로젝트 통합 대시보드 |
관리 프로젝트는 기존 프로젝트 멤버 역할 중 `PROJECT_MANAGER` 또는 `Admin`에 해당하는 프로젝트로 정의한다.
## 3. 라우팅
### 신규 통합 대시보드
```text
/main/overview
```
### 기존 프로젝트별 화면
```text
/main/project/[projectId]/dashboard
/main/project/[projectId]/feedback
/main/project/[projectId]/issue
/main/project/[projectId]/settings
```
통합 대시보드는 특정 `projectId` 없이 접근한다. 프로젝트별 상세 작업은 기존 프로젝트 라우트로 이동한다.
## 4. 화면 구성
### 4.1 전체 요약 카드
접근 가능한 프로젝트 전체를 기준으로 다음 지표를 표시한다. 카드와 Todo 목록은 같은 조회 기간·프로젝트 범위를 사용한다.
- 전체 피드백 건수
- 답변 대기 건수
- 이슈 연결률
- 평균 처리 시간
- 진행 중 이슈 건수
- 완료 피드백 건수
- 현재 작업공간에서 처리할 Todo 건수
- 담당자 미지정 건수
- 피드백 상태 처리 필요 건수
- 이슈 연결 또는 이슈 상태 처리 필요 건수
### 4.2 하위 탭
전역 작업공간은 처리 대상에 따라 두 개의 하위 탭으로 나눈다.
- **피드백 처리**: 오늘 등록 건수 등의 요약, 담당자 지정, 관리자 댓글 등록, 피드백 상태 처리, 연결 이슈 요약을 표시한다.
- **이슈 처리**: 프로젝트별 이슈를 중복 없이 묶어 Gitea 연결·상태 처리와 연결된 피드백 목록을 표시한다.
### 4.3 관리자 Todo 작업 목록
전역 작업공간의 핵심은 여러 프로젝트의 피드백을 한 목록에서 처리하는 것이다.
| 작업 | 처리 방식 |
| --- | --- |
| 프로젝트 확인 | 프로젝트별 피드백 화면 링크 제공 |
| 담당자 지정 | 해당 프로젝트의 지원 담당자 후보 조회 후 지정·해제 |
| 피드백 상태 | 기존 지원 티켓 상태 API로 상태 변경 |
| 이슈 연결 | 기존 프로젝트 이슈 검색 후 피드백과 연결·해제 |
| 이슈 상태 | 기존 6단계 이슈 상태로 변경 |
| Gitea 연결 | Gitea 검색 결과를 기존 이슈에 연결 |
| Gitea 상태 | 연결된 외부 이슈 상태 조회·새로고침 |
피드백 상태와 이슈 상태는 서로 독립적으로 처리한다. 이슈 상태 변경은 내부 이슈 상태를 기준으로 저장하며, Gitea 연결 이슈가 있는 경우 외부 상태 동기화를 시도한다.
### 4.4 프로젝트별 요약 테이블
| 항목 | 설명 |
| --- | --- |
| 프로젝트 | 프로젝트명 및 프로젝트 이동 링크 |
| 피드백 | 프로젝트의 전체 피드백 건수 |
| 답변 대기 | 답변 또는 이슈 처리가 완료되지 않은 피드백 건수 |
| 이슈 연결률 | 이슈가 연결된 피드백 비율 |
| 평균 처리 시간 | 처리 완료된 피드백의 평균 처리 시간 |
| 상태별 건수 | 피드백 상태별 건수 요약 |
| 최근 업데이트 | 해당 프로젝트의 최근 변경 시각 |
프로젝트 행 또는 프로젝트명을 클릭하면 해당 프로젝트의 기존 대시보드로 이동한다.
### 4.5 필터
다음 필터를 제공한다.
- 기간 필터
- 프로젝트 필터
- 담당자 지정 여부
- 피드백 상태
- 이슈 연결 여부
현재 단계에서는 행 단위 처리를 제공한다. 추후 현재 로그인 사용자 기준의 정확한 “내 담당” 필터, 일괄 상태 변경, 댓글·내부 메모 입력을 확장한다.
## 5. 백엔드 API 설계
프로젝트별 API를 프론트엔드에서 반복 호출하지 않고, 통합 집계 API를 제공한다.
```text
GET /api/admin/dashboard/overview
```
### 요청 파라미터
```text
from
to
projectIds[]
feedbackStatus
assigned
```
### 응답 예시
```json
{
"summary": {
"totalFeedback": 73,
"waitingReply": 16,
"issueLinkedRate": 0.58,
"averageProcessingHours": 20.4
},
"projects": [
{
"projectId": 1,
"projectName": "Q&A",
"feedbackCount": 55,
"waitingReplyCount": 12,
"issueLinkedRate": 0.67,
"averageProcessingHours": 24.5,
"statusCounts": {
"INIT": 10,
"IN_PROGRESS": 20,
"DONE": 25
}
}
]
}
```
## 6. 집계 기준
기존 프로젝트 대시보드와 동일한 기준을 사용한다.
- 이슈 연결률은 전체 피드백 중 이슈가 연결된 피드백의 비율로 계산한다.
- 답변 대기는 답변이 없거나 이슈 처리가 완료되지 않은 피드백 건수로 계산한다.
- 평균 처리 시간은 처리 완료된 피드백만 대상으로 계산한다.
- 여러 프로젝트의 평균 처리 시간은 프로젝트별 평균을 다시 평균내지 않고, 전체 처리 시간 합계를 전체 처리 완료 건수로 나누어 계산한다.
- 상태별 건수는 피드백 상태값 기준으로 집계한다.
- 이슈 상태와 피드백 상태는 서로 독립적으로 집계한다.
## 7. 권한 및 보안
통합 API는 서버에서 사용자의 프로젝트 접근 권한을 다시 검증해야 한다.
- 일반 관리자는 자신이 관리자로 등록된 프로젝트만 조회한다.
- `SUPER` 관리자는 전체 프로젝트를 조회할 수 있다.
- 요청한 `projectIds` 중 접근 권한이 없는 프로젝트는 결과에서 제외하거나 `403 Forbidden`으로 처리한다.
- 프론트엔드의 프로젝트 목록 제한은 UI 편의 기능일 뿐, 최종 권한 검증은 백엔드에서 수행한다.
현재 프로젝트 멤버십 및 역할 구조를 재사용하며, 1차 구현에서는 별도 상위 프로젝트 테이블을 추가하지 않는다.
## 8. 헤더 및 전역 홈 UI
통합 대시보드는 프로젝트 선택 항목이 아니라 전역 홈 버튼으로 접근한다.
- 홈 버튼: `/main/overview`
- 프로젝트 선택: 개별 프로젝트 화면 이동 전용
- 프로젝트 대시보드·피드백·이슈·설정: `projectId` 필요
이렇게 분리하면 프로젝트 선택값을 바꾸지 않고도 여러 프로젝트의 Todo를 처리할 수 있고, 기존 프로젝트별 작업 흐름도 유지된다.
## 9. 1차 구현 작업 순서
1. 통합 대시보드 API의 요청·응답 DTO 정의
2. 프로젝트 접근 권한 범위 조회 로직 구현
3. 여러 프로젝트 피드백·이슈 집계 서비스 구현
4. `/main/overview` 페이지 추가
5. 전체 요약 카드 구현
6. 프로젝트별 요약 테이블 구현
7. 기간·프로젝트·상태·내 담당 필터 구현
8. 로그인 후 기본 라우팅 분기 구현
9. 전역 홈 버튼 및 프로젝트 선택 UI 분리
10. Todo 작업 목록과 행 단위 명령 처리 구현
11. 이슈·Gitea 상태 동기화 API 연결
12. 권한·라우팅·집계 테스트 작성
## 10. 향후 확장
부서별 또는 서비스군별로 프로젝트를 직접 묶고, 상위 관리자가 그룹을 관리해야 하는 요구가 생기면 별도 그룹 엔티티를 추가한다.
```text
portfolio
└── projects
```
현재 단계에서는 DB에 상위 프로젝트 개념을 추가하지 않고, 사용자의 프로젝트 접근 권한을 기반으로 한 가상 통합 대시보드로 구현한다.
## 11. 1차 구현 상태
전역 관리자 작업공간의 1차 기능을 적용했다. 프로젝트별 화면과 기존 권한 검증은 유지하고, `/main/overview`에서 접근 가능한 프로젝트의 Todo를 통합 조회·처리한다.
- [x] `/api/admin/dashboard/overview` 집계 API에 `todos` 응답 추가
- [x] 관리자별 프로젝트 접근 권한 검증
- [x] 프로젝트별 피드백·이슈 집계
- [x] `/main/overview` 전역 작업공간 화면 추가
- [x] 홈 버튼으로 전역 화면 접근, 프로젝트 선택과 분리
- [x] 전체 요약 카드 및 프로젝트별 요약 테이블 유지
- [x] 기간·프로젝트·담당자 지정·피드백 상태·이슈 연결 필터 추가
- [x] 피드백 담당자 지정·해제
- [x] 피드백 상태 변경
- [x] 관리자 댓글 등록
- [x] 이슈 연결·해제 및 내부 이슈 6단계 상태 변경
- [x] Gitea 이슈 검색·연결 및 외부 상태 새로고침
- [x] 내부 이슈 상태 변경 시 연결된 Gitea 이슈 동기화 시도
- [x] 피드백 상태와 이슈 상태를 독립적으로 처리하는 구조 유지
현재 Todo 목록은 프로젝트별 최근 피드백을 최대 500건까지 집계하며, 대량 데이터에 대한 서버 페이지네이션·가상 스크롤은 후속 작업이다. 내부 메모·일괄 처리와 이슈 처리 탭의 추가 일괄 작업은 기존 프로젝트별 API 권한을 재사용해 다음 단계로 확장한다.
+103
View File
@@ -0,0 +1,103 @@
# 네이버웍스 피드백 알림 정책
- 작성일: 2026-08-28
- 적용 범위: ABC UserFeedback 피드백 알림
- 목적: 피드백 이벤트를 필요한 사용자에게만 전달하고, 중복·오발송·민감정보 노출을 방지한다.
## 1. 확정된 이벤트별 수신자
| 이벤트 | 수신자 | 발송 조건 |
| --- | --- | --- |
| 신규 피드백 등록 | 피드백 담당자, 프로젝트 관리자, 피드백 작성자 | 피드백이 실제로 새로 등록된 경우 |
| 피드백 상태 변경 | 피드백 담당자, 프로젝트 관리자, 피드백 작성자 | 이전 상태와 현재 상태가 실제로 다른 경우 |
| 관리자 공개 댓글 등록 | 해당 피드백 작성자 | 공개 댓글이 실제로 등록된 경우 |
### 수신자 해석 기준
- 피드백 담당자는 해당 피드백에 지정된 담당자다.
- 프로젝트 관리자는 해당 프로젝트·채널에 `PROJECT_MANAGER`로 지정된 활성 사용자다.
- 피드백 작성자는 SSO의 작성자 계정과 네이버웍스 사용자 매핑이 확인된 경우에만 수신 대상이 된다.
- 수신자 정보는 이벤트 payload의 임의 이메일을 그대로 사용하지 않고, 서버가 프로젝트·채널·피드백 데이터로 확정한다.
- 동일 사용자가 여러 역할에 해당하면 네이버웍스 알림은 한 번만 발송한다.
- 담당자, 프로젝트 관리자, 작성자 중 지정되지 않았거나 비활성인 사용자는 수신 대상에서 제외한다.
- 시스템 관리자라는 이유만으로 모든 프로젝트 알림을 받지는 않는다. 해당 프로젝트의 프로젝트 관리자 또는 피드백 담당자로 지정된 경우에만 받는다.
## 2. 알림을 보내지 않는 이벤트
- 내부 메모 등록·수정·삭제: 외부 사용자에게 발송하지 않는다.
- 관리자 공개 댓글 수정·삭제: 별도 정책 확정 전까지 발송하지 않는다.
- 이슈 상태 변경 및 Gitea 상태 변경: 피드백 알림과 독립적인 이벤트로 취급하며, 별도 수신자·메시지 정책 확정 전까지 이 정책으로 발송하지 않는다.
- 피드백과 이슈의 상태는 서로 독립적이다. 이슈 상태가 바뀌었다고 피드백 상태 변경 알림을 보내지 않는다.
- 같은 상태로 다시 저장한 경우 상태 변경 알림을 보내지 않는다.
- 알림 연동 Bot 또는 시스템 계정이 생성한 이벤트는 재귀 알림 방지를 위해 관리자 댓글 알림 대상에서 제외한다.
## 3. 이벤트별 발송 규칙
### 3.1 신규 피드백
- `feedback.created` 이벤트만 처리한다.
- 담당자·프로젝트 관리자·작성자를 합친 뒤 이메일 또는 사용자 ID 기준으로 중복 제거한다.
- 담당자와 프로젝트 관리자는 Secretary DB의 프로젝트·채널 권한 원본을 기준으로 조회한다.
- 작성자에게 보내는 메시지는 비밀글 여부를 확인하고, 비밀글의 제목·본문·댓글 원문을 포함하지 않는다.
### 3.2 피드백 상태 변경
- `feedback.status_changed` 이벤트만 처리한다.
- `previousStatus``currentStatus`가 같으면 발송하지 않는다.
- `완료`, `진행하지 않음`을 포함한 모든 유효한 피드백 상태 변경을 동일한 정책으로 처리한다.
- 메시지에는 피드백 ID, 변경 전 상태, 변경 후 상태, 상세 페이지 링크를 포함할 수 있다.
- 이슈 상태 변경이나 Gitea 동기화 결과만으로는 이 이벤트를 생성하지 않는다.
### 3.3 관리자 공개 댓글
- `feedback.admin_comment_created` 이벤트만 처리한다.
- 수신자는 해당 피드백 작성자 한 명으로 제한한다.
- 담당자, 프로젝트 관리자, 시스템 관리자, 기본 네이버웍스 방에는 보내지 않는다.
- 댓글 작성자가 내부 메모를 등록한 경우 이 이벤트를 생성하지 않는다.
- 댓글 본문은 길이 제한과 평문화 후 전송하고, 민감정보가 포함되지 않도록 최소 정보 원칙을 적용한다.
## 4. 중복·재전송 방지
- ABC가 제공한 `eventId`를 우선 사용한다.
- `eventId + 수신자 + 알림 템플릿 버전`을 발송 중복 판단 키로 사용한다.
- 동일 이벤트를 다시 수신해도 같은 수신자에게 한 번만 발송한다.
- 이벤트 처리 성공과 네이버웍스 발송 성공은 별도로 기록한다.
- 네트워크 오류, timeout, `408`, `429`, `5xx`만 제한적으로 재시도한다.
- `400`, `403`, 잘못된 사용자 매핑 등 영구 오류는 재시도하지 않는다.
- 오래된 이벤트나 현재 ABC 데이터와 다른 이벤트는 현재 데이터를 재조회한 뒤 무시하거나 격리한다.
## 5. 오발송 차단 및 개인정보 보호
- 사용자별 Bot 메시지 발송을 기본으로 하며, 기본 방 전체 발송으로 대체하지 않는다.
- 프로젝트·채널 범위를 벗어난 관리자나 수신자에게 발송하지 않는다.
- 수신자 매핑에 실패하면 임의 사용자나 전체 방으로 보내지 않고 실패 이력만 남긴다.
- 메시지에는 업무에 필요한 최소 정보만 포함한다.
- SSO 토큰, Webhook 인증값, 네이버웍스 access token·client secret·private key는 메시지와 로그에 남기지 않는다.
- 전화번호, IP 주소, MAC 주소는 기본 알림 메시지에 포함하지 않는다.
- 비밀글은 권한이 확인된 상세 링크와 최소 식별 정보만 전송한다.
- Webhook 인증 실패 요청의 원문 payload와 개인정보는 로그에 기록하지 않는다.
## 6. 메시지 기본 구성
알림 메시지는 다음 항목을 기본으로 사용한다.
- 이벤트 종류
- 프로젝트명·채널명
- 피드백 ID
- 피드백 제목 또는 상태 변경 정보
- 변경 전·후 상태(상태 변경 이벤트인 경우)
- 댓글 내용 일부(관리자 공개 댓글인 경우)
- 피드백 상세 페이지 링크
비밀글 또는 개인정보가 포함될 수 있는 경우 제목·본문·댓글 원문을 생략한다.
## 7. 운영 확인 항목
- [ ] 프로젝트 관리자가 Secretary DB에서 해당 프로젝트·채널의 `PROJECT_MANAGER`로 활성 지정되어 있다.
- [ ] 피드백 담당자와 작성자의 네이버웍스 계정 매핑이 확인되어 있다.
- [ ] 신규 피드백 등록 시 담당자·프로젝트 관리자·작성자에게 각각 한 번만 도착한다.
- [ ] 피드백 상태를 `완료` 또는 `진행하지 않음`으로 변경했을 때도 한 번만 도착한다.
- [ ] 관리자 공개 댓글은 작성자에게만 도착한다.
- [ ] 내부 메모와 이슈 상태 변경은 이 정책에 따라 발송되지 않는다.
- [ ] 같은 상태 재저장, Webhook 재전송, 동일 수신자 중복 등록 시 중복 메시지가 발생하지 않는다.
- [ ] 수신자 매핑 실패 시 전체 방으로 잘못 발송되지 않는다.
@@ -0,0 +1,429 @@
# ABC Webhook–네이버웍스 알림 연동 작업 계획
- 작성일: 2026-08-26
- 목적: ABC UserFeedback에서 발생한 피드백 이벤트를 웹훅으로 수신하고, 네이버웍스 Bot API를 통해 관리자에게 알림을 보낸다.
- 1차 범위: 신규 피드백 등록, 피드백 상태 변경, 관리자 공개 댓글 등록
- 현재 상태: 핵심 구현 완료, 로컬 검증 완료. 네이버웍스 실발송과 ABC 외부 Webhook 실연동은 자격증명·운영 설정 후 검증 필요
## 1. 목표 시나리오
```text
ABC UserFeedback
-> ABC Webhook 이벤트 발송
-> 우리 백엔드 웹훅 수신 API
-> 서명·토큰 검증 및 중복 요청 검사
-> 이벤트 표준화
-> 알림 대상자·메시지 결정
-> 네이버웍스 Bot API 호출
-> 발송 결과와 실패 이력 저장
```
### 1.1 1차 이벤트
| 이벤트 | 알림 제목 | 기본 수신 대상 |
| --------------------- | ---------------- | --------------------------------------------- |
| 신규 피드백 등록 | 신규 피드백 등록 | 피드백 담당자, 프로젝트 관리자, 피드백 작성자 |
| 피드백 상태 변경 | 피드백 상태 변경 | 피드백 담당자, 프로젝트 관리자, 피드백 작성자 |
| 관리자 공개 댓글 등록 | 관리자 댓글 등록 | 해당 피드백 작성자만 |
내부 메모는 일반 댓글과 공개 범위가 다르므로 1차 범위에서는 알림 대상에서 제외한다. 내부 메모를 네이버웍스로 보내려면 관리자 전용 수신 정책을 별도로 확정해야 한다.
## 2. 현재 구조 확인 사항
- [x] NestJS API에 프로젝트별 Webhook 등록·조회·수정·삭제 API가 존재함
- [x] Webhook에 이벤트 종류와 프로젝트·채널 범위를 설정하는 구조가 존재함
- [x] 실제 피드백 등록·상태 변경·관리자 공개 댓글 등록 이벤트를 내부 알림 흐름에 연결
- [x] 외부 Webhook 수신 전용 라우터 구현
- [ ] 현재 Webhook payload 형식과 이벤트별 예시 확보
- [x] Webhook 인증 방식(서명 또는 토큰) 구현
- [x] 네이버웍스 Bot API 인증·발송 어댑터 구현
현재 Webhook 관리 API가 있다는 것만으로는 이벤트 발송과 네이버웍스 전송이 완성된 상태가 아니다. 발송 지점과 payload 계약을 먼저 확인한 뒤 수신 API를 연결한다.
## 3. 권장 구현 구조
### 3.1 웹훅 수신 계층
추후 이벤트가 늘어나도 라우터를 변경하지 않도록 단일 수신 엔드포인트에서 처리한다.
권장 경로 예시:
POST /api/integrations/abc/webhooks
수신 처리 순서:
1. 원문 body와 인증 헤더를 확보한다.
2. Webhook 토큰 또는 서명을 검증한다.
3. `eventId` 또는 payload hash로 중복 요청을 검사한다.
4. 이벤트 타입을 허용 목록과 비교한다.
5. 이벤트별 payload를 내부 표준 이벤트로 변환한다.
6. 빠르게 `2xx` 응답을 반환하고 알림 발송을 처리한다.
7. 발송 결과를 로그에 기록하고 실패 시 재시도 대상으로 남긴다.
인증에 실패한 요청은 상세 payload를 로그에 남기지 않고 `401` 또는 `403`으로 응답한다. 이벤트 처리 실패와 네이버웍스 일시 장애는 Webhook 자체 인증 실패와 구분한다.
### 3.2 표준 이벤트 모델
외부 ABC payload에 직접 의존하지 않고 다음과 같은 내부 모델로 변환한다.
```ts
type NotificationEventType =
| 'feedback.created'
| 'feedback.status_changed'
| 'feedback.admin_comment_created';
interface NotificationEvent {
eventId: string;
type: NotificationEventType;
occurredAt: string;
projectId: number;
channelId: number;
feedbackId: number;
title?: string;
feedbackUrl?: string;
actor?: {
id?: string;
name?: string;
email?: string;
};
status?: {
previous?: string;
current: string;
};
comment?: {
id?: number;
content: string;
isInternal: boolean;
};
}
```
이벤트 변환기와 메시지 템플릿을 분리해 ABC payload가 변경되거나 이벤트가 추가되어도 네이버웍스 클라이언트는 수정하지 않도록 한다.
### 3.3 네이버웍스 연동 계층
다음 인터페이스를 기준으로 네이버웍스 구현체를 분리한다.
```ts
interface NotificationSender {
send(message: NotificationMessage): Promise<NotificationSendResult>;
}
```
권장 구성:
- `WebhookReceiver`: ABC Webhook 검증·수신
- `NotificationEventMapper`: 외부 payload를 표준 이벤트로 변환
- `NotificationRouter`: 이벤트별 수신자 결정
- `NaverWorksClient`: 인증 토큰 발급·갱신 및 메시지 API 호출
- `NaverWorksMessageBuilder`: 이벤트별 메시지 조합
- `NotificationLogService`: 요청·성공·실패·재시도 이력 기록
현재 라우팅 구현은 SSO/ABC 피드백 데이터의 이메일을 NAVER WORKS 로그인 ID로 사용한다. 신규 피드백과 상태 변경은 `assignee_email`, Secretary DB에서 프로젝트·채널 매핑으로 조회한 `PROJECT_MANAGER`, ABC 프로젝트 멤버의 관리자 이메일, `requester_email`을 합친 뒤 대소문자 기준으로 중복 제거한다. 관리자 공개 댓글은 `requester_email`만 대상으로 한다. 사용자별 발송은 `/bots/{botId}/users/{userId}/messages`를 사용하고 기본 방 발송으로 대체하지 않는다. 프로젝트 관리자 지정의 원본은 Secretary DB이며, ABC API는 `x-api-key`로 보호된 내부 수신자 조회 API를 통해서만 이를 읽는다.
## 4. 필요한 환경변수
실제 변수명은 기존 환경변수 규칙에 맞춰 확정한다. 비밀값은 코드, `.env.example`, README, 채팅에 입력하지 않는다.
### 4.1 ABC Webhook 수신
```dotenv
ABC_WEBHOOK_ENABLED=false
ABC_WEBHOOK_PATH=/api/integrations/abc/webhooks
ABC_WEBHOOK_TOKEN=
ABC_WEBHOOK_SIGNING_SECRET=
ABC_WEBHOOK_ALLOWED_PROJECT_IDS=
SUPPORT_API_BASE_URL=http://127.0.0.1:8010
MASTER_API_KEY=
```
`TOKEN` 방식과 서명 방식 중 ABC가 실제로 제공하는 인증 방법만 활성화한다. 둘 다 지원할 경우 운영에서는 서명 검증을 우선한다.
### 4.2 네이버웍스 Bot API
```dotenv
NAVER_WORKS_ENABLED=false
NAVER_WORKS_API_BASE_URL=
NAVER_WORKS_AUTH_URL=
NAVER_WORKS_BOT_ID=
NAVER_WORKS_DOMAIN_ID=
NAVER_WORKS_CLIENT_ID=
NAVER_WORKS_CLIENT_SECRET=
NAVER_WORKS_SERVICE_ACCOUNT=
NAVER_WORKS_PRIVATE_KEY=
NAVER_WORKS_DEFAULT_ROOM_ID=
NAVER_WORKS_DEFAULT_USER_ID=
```
필요한 값은 네이버웍스 인증 방식에 따라 달라질 수 있다. 특히 `client secret`, 서비스 계정, 개인키는 Gitea Secrets 또는 스테이징 서버의 비밀 환경변수로만 등록한다.
### 4.3 알림 처리 정책
```dotenv
NOTIFICATION_DELIVERY_MODE=async
NOTIFICATION_MAX_RETRIES=3
NOTIFICATION_RETRY_BASE_DELAY_MS=1000
NOTIFICATION_LOG_ENABLED=true
NOTIFICATION_INCLUDE_FEEDBACK_URL=true
NOTIFICATION_MAX_BODY_LENGTH=10000
NOTIFICATION_RATE_LIMIT_PER_MINUTE=60
NOTIFICATION_CIRCUIT_BREAKER_FAILURES=5
NOTIFICATION_KILL_SWITCH=false
```
## 5. 알림 정책
### 5.1 수신 대상
확정된 1차 수신 정책은 다음과 같다.
| 이벤트 | 수신 대상 | 발송 기준 |
| --------------------- | --------------------------------------------- | ------------------------------------------------ |
| 신규 피드백 등록 | 피드백 담당자, 프로젝트 관리자, 피드백 작성자 | 동일 사용자가 여러 역할에 해당하면 한 번만 발송 |
| 피드백 상태 변경 | 피드백 담당자, 프로젝트 관리자, 피드백 작성자 | 실제 상태가 변경된 경우에만 발송 |
| 관리자 공개 댓글 등록 | 해당 피드백 작성자 | 담당자·프로젝트 관리자·기본 방에는 발송하지 않음 |
- 피드백 담당자와 프로젝트 관리자는 해당 프로젝트·채널 범위에 한정한다.
- 피드백 작성자는 SSO 사용자와 네이버웍스 계정 매핑이 확인된 경우에만 발송한다.
- 동일 사용자가 담당자·프로젝트 관리자·작성자 역할을 여러 개 가지고 있어도 중복 발송하지 않는다.
- 담당자 또는 프로젝트 관리자가 지정되지 않은 경우 해당 역할로는 발송하지 않는다.
- 작성자 계정 매핑에 실패한 경우 임의의 사용자나 전체 방으로 대체 발송하지 않고 실패 이력으로 남긴다.
- 비밀글의 사용자 알림에는 제목·본문·댓글 내용을 포함하지 않고, 권한 검증이 가능한 상세 링크와 최소 정보만 사용한다.
### 5.2 이벤트 조건
- 실제 상태가 변경된 경우에만 발송할지
- 상태를 같은 값으로 다시 저장할 때는 발송하지 않을지
- 관리자 공개 댓글만 발송하고 내부 메모는 제외할지
- 댓글 수정·삭제도 알림을 보낼지
- 이슈 상태 변경과 Gitea 이벤트를 1차 범위에 포함할지
### 5.3 메시지 정책
기본 메시지에는 다음 정보를 포함하는 것을 권장한다.
- 프로젝트명·채널명
- 피드백 ID
- 피드백 제목
- 변경 전·후 상태
- 댓글 작성자와 댓글 내용 일부
- 피드백 상세 페이지 링크
비밀글은 네이버웍스 메시지에 본문을 포함하지 않고, 권한이 있는 사용자가 상세 페이지에서 확인하도록 한다. 댓글은 개인정보와 긴 본문 노출을 막기 위해 길이 제한과 마스킹 정책을 둔다.
### 5.4 비정상 Webhook 차단 정책
Webhook은 인터넷에 노출될 수 있는 입력 경계로 취급한다. 아래 조건을 만족하지 않는 요청은 이벤트 처리와 네이버웍스 발송을 진행하지 않는다.
| 상황 | 처리 방법 | 목적 |
| --------------------------------------- | ------------------------------------------------ | ------------------------------ |
| 인증 토큰·서명 없음 | `401/403` 응답, payload 미기록 | 위조 요청 차단 |
| 서명 불일치 | `401/403` 응답, 보안 이벤트만 기록 | payload 변조 차단 |
| 서명 timestamp가 허용 시간 초과 | 거부 | 캡처 payload 재전송 차단 |
| 지원하지 않는 `eventType` | 인증 후 `202`로 무시하고 이벤트 타입만 기록 | 불필요한 재시도·오류 폭주 방지 |
| 필수 식별자 누락 | `400`, 알림 미발송 | 잘못된 대상 발송 방지 |
| 존재하지 않는 프로젝트·채널·피드백 | 검증 후 무시 또는 격리 | 삭제·오래된 데이터 오발송 방지 |
| 허용 목록 밖의 프로젝트 | 무시, 보안 로그 기록 | 다른 프로젝트 데이터 유출 방지 |
| 허용 목록 밖의 수신자 | 발송하지 않고 격리 | 관리자 외 대상 오발송 방지 |
| 너무 큰 body 또는 잘못된 Content-Type | `413/415` | 리소스 고갈·파싱 공격 방지 |
| 같은 이벤트 재수신 | idempotency key로 한 번만 발송 | 중복 메시지 방지 |
| 오래된 이벤트 또는 순서가 뒤바뀐 이벤트 | event version 검증 후 무시 또는 최신 상태 재조회 | 과거 상태로 되돌아간 알림 방지 |
인증 실패 요청에는 원문 body, 댓글 내용, 이메일, IP/MAC 등의 개인정보를 로그로 남기지 않는다. 반복적인 인증 실패는 IP·경로 단위 rate limit 또는 차단 대상으로 분류한다.
### 5.5 이벤트 유효성 및 중복 처리
- 이벤트 식별자는 ABC가 제공하는 `eventId`를 우선 사용하고, 없으면 `payload hash + occurredAt` 조합으로 생성한다.
- 중복 기준은 `eventId + notification target + template version`으로 한다. 수신자별 발송이 필요한 경우 한 이벤트를 대상자마다 한 번만 처리한다.
- 상태 변경은 `previousStatus``currentStatus`가 같으면 알림을 보내지 않는다.
- 이벤트가 현재 ABC 데이터와 일치하지 않으면 payload만 믿지 않고 피드백 상세를 재조회한다.
- 이미 더 최신 상태가 처리된 경우 오래된 이벤트는 폐기한다.
- 댓글 이벤트는 댓글 ID를 기준으로 중복 처리하며, 수정·삭제 이벤트는 별도 허용 목록에 추가하기 전까지 무시한다.
- Webhook 수신 성공과 네이버웍스 발송 성공은 별도 상태로 기록한다. 수신 성공을 발송 성공으로 간주하지 않는다.
### 5.6 수신자 및 권한 안전장치
- 수신자는 이벤트 payload가 지정한 임의의 주소를 그대로 사용하지 않고, 프로젝트·채널·담당자 정보를 기준으로 서버에서 결정한다.
- 네이버웍스 방 ID와 사용자 ID는 프로젝트별 allowlist에 등록된 값만 사용한다.
- 담당자 정보가 없거나 비활성 사용자인 경우 전체 관리자 방으로 자동 전송하지 않는다. 별도 격리 대상 또는 명시된 기본 운영자에게만 보낸다.
- 작성자에게 알림을 보낼 때는 SSO 계정과 네이버웍스 계정의 매핑이 확인된 경우에만 발송한다.
- 비밀글의 제목·본문·댓글 내용은 사용자용 방에 포함하지 않는다. 필요한 경우 관리자 전용 방에 최소 정보만 보낸다.
- 내부 메모는 공개 댓글과 다른 이벤트로 취급하고, 관리자 전용 정책이 확정되기 전까지 외부 알림을 금지한다.
- 프로젝트별 환경과 수신자 설정을 분리해 다른 프로젝트의 알림 방으로 섞이지 않도록 한다.
### 5.7 재시도·장애·폭주 제어
- 재시도 대상은 네트워크 오류, timeout, `408`, `429`, `5xx`로 제한한다.
- `400`, `403`, 잘못된 수신자 등 영구 오류는 재시도하지 않고 실패 이력과 원인을 저장한다.
- `401`은 토큰을 한 번 갱신한 뒤 한 차례만 재요청한다. 반복 인증 실패는 즉시 중단한다.
- 지수 backoff와 jitter를 사용하고, 최대 재시도 횟수를 넘으면 격리 큐 또는 재처리 목록에 저장한다.
- 네이버웍스 장애가 지속되면 circuit breaker를 열어 즉시 실패시키고, ABC Webhook 수신 자체는 정상적으로 종료한다.
- 짧은 시간에 이벤트가 급증하면 사용자별·프로젝트별·전체 발송량 제한을 적용한다.
- 동일 피드백의 연속 상태 변경은 짧은 시간 동안 묶음 알림 또는 마지막 상태 알림으로 축약할 수 있다. 단, 이 정책은 업무상 누락이 허용되는지 확인한 뒤 적용한다.
- 실패한 발송을 무한 재시도하지 않으며, 관리자에게 재처리 필요 건수만 별도로 알린다.
### 5.8 무한 루프 및 자기 자신이 만든 이벤트 차단
- 네이버웍스 발송 결과나 Bot이 남긴 메시지가 ABC 피드백 이벤트로 되돌아오는 구조인지 확인한다.
- 이벤트 actor가 연동용 Bot 또는 시스템 계정이면 관리자 댓글 알림 대상에서 제외한다.
- 발신 Webhook과 수신 Webhook URL이 동일하거나 서로 재호출하는 구성이 되지 않도록 배포 시 검사한다.
- 알림 메시지에 포함된 링크 조회는 이벤트를 다시 생성하지 않는 읽기 전용 경로를 사용한다.
- 연동별 `source``correlationId`를 기록해 동일 연동에서 발생한 재귀 호출을 탐지한다.
### 5.9 개인정보·로그·운영 안전
- 네이버웍스 메시지에는 업무 처리에 필요한 최소 정보만 포함한다.
- 이메일, 전화번호, IP, MAC 주소, SSO 토큰, Webhook 원문 인증값은 메시지와 일반 로그에서 제외한다.
- 댓글 본문은 최대 길이를 제한하고 HTML·스크립트·제어문자를 제거한 평문으로 변환한다.
- 로그에는 `eventId`, 프로젝트 ID, 피드백 ID, 처리 상태, 실패 코드, correlation ID만 기본 저장한다.
- access token, client secret, private key, Webhook secret은 로그·에러 응답·Swagger 예시에 절대 포함하지 않는다.
- 스테이징 Bot·방과 운영 Bot·방의 자격증명 및 수신자 allowlist를 분리한다.
- 긴급 상황에서 신규 발송만 중지할 수 있는 `NOTIFICATION_KILL_SWITCH`를 제공하고, 수신 이벤트와 실패 이력은 보존한다.
- 설정 변경과 kill switch 활성화·해제 이력은 관리자 감사 로그에 남긴다.
### 5.10 ACK 및 오류 응답 정책
Webhook 제공자가 재전송하는 조건을 고려해 응답을 다음과 같이 고정한다.
| 조건 | 응답 | 후속 처리 |
| ----------------------- | --------: | ---------------------------------- |
| 인증 실패 | `401/403` | 처리하지 않음 |
| body 형식 오류 | `400` | 처리하지 않음 |
| 허용되지 않은 이벤트 | `202` | 무시 이력만 저장 |
| 인증된 신규 이벤트 접수 | `202` | 비동기 발송 |
| 중복 이벤트 | `200/202` | 기존 결과 재사용, 재발송하지 않음 |
| 내부 큐 저장 실패 | `503` | 제공자 재전송 유도, 장애 로그 저장 |
네이버웍스 발송이 실패했다는 이유로 이미 수신한 Webhook을 무조건 `5xx`로 응답하지 않는다. 그렇지 않으면 ABC가 같은 이벤트를 반복 전송하여 중복 알림이 발생할 수 있다.
## 6. 구현 전 확정 항목
다음 항목은 구현 전에 업무 담당자와 확정한다.
- [x] 신규 피드백·상태 변경·댓글별 최종 수신 대상
- 담당자 미지정·계정 매핑 실패·프로젝트 설정 누락 시의 격리 대상
- 피드백 상태 변경과 이슈 상태 변경을 같은 알림으로 묶을지 여부
- 비밀글·내부 메모의 네이버웍스 전송 허용 범위
- 상태가 짧은 시간에 여러 번 바뀔 때 즉시 발송할지 묶음 발송할지
- 댓글 본문 최대 길이와 개인정보 마스킹 규칙
- 실패 알림을 네이버웍스 외 별도 채널로 보낼지 여부
- 발송 로그 보관 기간과 재처리 권한
- 스테이징과 운영의 Bot, 방, 수신자, Secret 분리 방식
## 7. 작업 단계
### Phase 1. 연동 계약 확인
- [x] ABC Webhook 이벤트 타입과 내부 이벤트 이름 추가
- [ ] 신규 등록·상태 변경·댓글 등록 payload 샘플 확보
- [x] Webhook 수신 인증 방식 구현: HMAC 서명 우선, 토큰 방식 지원
- [x] 네이버웍스 Bot API 인증 방식과 메시지 API 확인
- [ ] 관리자 방 또는 사용자별 수신자 식별자 확보
- [ ] 스테이징용 네이버웍스 Bot과 테스트 방 지정
### Phase 2. 웹훅 수신 API
- [x] Webhook 수신 DTO와 이벤트 허용 목록 작성
- [x] 토큰·서명 검증 구현
- [x] timestamp·body size·Content-Type 기본 검증 구현
- [x] 프로젝트 allowlist 검증 구현
- [x] 요청 ID와 중복 이벤트 방지 구현
- [ ] 오래된 이벤트·순서 역전·동일 상태 변경 차단 구현
- [x] 이벤트 표준화 및 메시지 변환 구현
- [x] 인증 실패·잘못된 payload·처리 실패 오류 응답 정의
- [x] 수신 API를 Swagger에서 제외해 인증정보·운영 입력값 노출 방지
### Phase 3. 네이버웍스 클라이언트
- [x] 액세스 토큰 발급·캐시·만료 갱신 구현
- [x] Bot 메시지 발송 함수 구현
- [x] API rate limit·일시 오류 기본 재시도 처리
- [ ] 4xx·5xx별 재시도 정책과 circuit breaker 구현
- [ ] 테스트용 발송 기능 구현
- [x] 비밀값이 로그에 노출되지 않도록 구현
### Phase 4. 이벤트별 알림
- [x] 신규 피드백 메시지 템플릿 구현
- [x] 상태 변경 메시지 템플릿 구현
- [x] 피드백 상태 변경 저장 경로에서 실제 변경 시 상태 이벤트 발생 구현
- [x] 관리자 공개 댓글 메시지 템플릿 구현
- [x] 담당자·프로젝트 관리자·피드백 작성자 라우팅 구현
- [x] 이벤트별 중복 수신자 제거 및 계정 매핑 실패 격리 처리 구현
- [x] 비밀글·내부 메모 공개 범위 처리
- [x] 상세 페이지 링크 생성
### Phase 5. 발송 이력과 장애 대응
- [x] 알림 발송 로그 모델 추가
- [x] 성공·실패·재시도 상태 저장
- [x] 중복 발송 방지용 idempotency key 저장
- [x] 네이버웍스 장애 시 원본 이벤트 유실 방지
- [ ] 발송량 제한·폭주 제어·kill switch 구현
- [x] 내부 메모리·Bot 자기 이벤트의 외부 알림 차단
- [ ] 관리자 화면 또는 운영 로그에서 실패 건 확인 가능하게 구성
### Phase 6. 검증 및 배포
- [ ] 단위 테스트: 서명 검증·이벤트 변환·템플릿·라우팅
- [ ] 통합 테스트: Webhook 수신부터 네이버웍스 client mock 발송까지
- [ ] 스테이징 실제 Bot 방으로 신규 피드백 테스트
- [ ] 상태 변경 중복 발송 테스트
- [ ] 공개 댓글과 내부 메모의 수신 범위 테스트
- [ ] 네이버웍스 인증 실패·timeout·재시도 테스트
- [ ] 위조 서명·오래된 timestamp·중복·순서 역전 이벤트 테스트
- [ ] 허용되지 않은 프로젝트·수신자·비밀글·내부 메모 차단 테스트
- [ ] 폭주·circuit breaker·kill switch·재처리 테스트
- [x] Gitea Actions의 Variables/Secrets를 스테이징 API 컨테이너까지 전달하도록 배포 설정 연결
- [ ] 스테이징 환경변수와 HTTPS 외부 Webhook URL 확인
- [ ] 운영 전 비밀값 회전 및 테스트 로그 정리
### 6.1 로컬 적용 및 검증 결과 (2026-08-26)
- [x] `notification_deliveries` 마이그레이션 로컬 적용 확인
- [x] `GET http://127.0.0.1:4000/api/health` 응답 `200`, database `up` 확인
- [x] `POST http://127.0.0.1:4000/api/integrations/abc/webhooks` 응답 `202`, 로컬 발송 비활성 응답 확인
- [x] API·웹 타입체크 및 린트 통과
- [x] 기존 Webhook listener 단위 테스트 22건 통과
- [x] 완료·진행하지 않음을 포함한 상태 변경 이벤트 중복 발송 방지 로직 보완 및 API 정적 검사 통과
- [ ] 네이버웍스 실제 Bot 메시지 발송 확인 — 로컬 자격증명 미설정
- [ ] ABC에서 외부 Webhook을 실제로 보내는 end-to-end 확인 — ABC 관리자 설정 필요
## 8. 완료 기준
- [ ] 허용된 ABC Webhook만 인증 후 처리된다.
- [ ] 신규 피드백 등록 시 지정된 네이버웍스 수신자에게 한 번만 알림이 도착한다.
- [ ] 피드백 상태가 실제로 변경될 때 이전 상태와 현재 상태가 포함된 알림이 도착한다.
- [ ] 관리자 공개 댓글 등록 시 정책에 맞는 수신자에게 알림이 도착한다.
- [ ] 내부 메모가 작성자에게 노출되지 않는다.
- [ ] 네이버웍스 장애가 발생해도 Webhook 요청과 발송 실패 이력이 유실되지 않는다.
- [x] 토큰·시크릿·개인키가 소스, API 응답, 로그에 노출되지 않는다.
- [ ] 새로운 이벤트를 추가할 때 수신 라우터의 핵심 흐름을 변경하지 않고 이벤트 매퍼·라우터·템플릿만 추가할 수 있다.
## 9. 사용자가 별도로 해야 하는 작업
1. 네이버웍스 Bot과 테스트용 방을 만들고 Bot을 방에 초대한다.
2. 네이버웍스 개발자 콘솔에서 Bot 메시지 발송 권한과 인증정보를 발급한다.
3. 로컬 `.env`에 네이버웍스 인증정보를 직접 입력한다. 값은 저장소·README·채팅에 올리지 않는다.
4. 로컬 검증 시 다음 플래그를 켠 뒤 API를 재시작한다.
ABC_WEBHOOK_ENABLED=true
ABC_WEBHOOK_TOKEN=<ABC에서 설정한 수신 토큰>
NAVER_WORKS_ENABLED=true
NAVER_WORKS_BOT_ID=<Bot ID>
NAVER_WORKS_DEFAULT_ROOM_ID=<테스트 방 ID>
HMAC 서명을 사용할 경우 `ABC_WEBHOOK_TOKEN` 대신 `ABC_WEBHOOK_SIGNING_SECRET`을 설정한다.
5. ABC 관리자 화면에서 Webhook URL을 `https://<외부주소>/api/integrations/abc/webhooks`로 등록하고, 신규 피드백·상태 변경·관리자 공개 댓글 이벤트를 선택한다.
6. 신규 피드백 등록, 피드백 상태 변경, 관리자 공개 댓글 등록을 각각 1회씩 실행해 네이버웍스 수신 여부와 `notification_deliveries` 기록을 확인한다.
7. 내부 메모, 비밀글, 중복 Webhook, 잘못된 토큰 요청이 알림으로 전송되지 않는지 확인한다.
## 10. 우선 결정할 입력값
구현 시작 전에 다음 네 가지만 먼저 확정한다.
1. 네이버웍스 수신 방식: 특정 방 1개인지, 사용자별 Bot 메시지인지
2. 신규 피드백·상태 변경·댓글별 수신 대상
3. ABC Webhook의 실제 payload와 인증 방식
4. 스테이징에서 사용할 외부 Webhook URL과 네이버웍스 테스트 방
@@ -0,0 +1,293 @@
# Q&A 관리자 콘솔 피드백 개선 작업 정리
대상 화면: [`qna-platform-prototype-2.html`](../qna-platform-prototype-2.html)
## 1. 작업 목표
관리자 콘솔에서 피드백을 접수한 뒤 담당자 배정, 내부 협업, 기존 이슈 연결 또는 신규 Gitea 이슈 생성, 고객 답변 및 처리 완료까지 한 흐름으로 관리할 수 있도록 개선한다.
이번 작업은 화면 목업의 방향을 기준으로 하되, 현재 백엔드에 이미 존재하는 티켓·댓글·첨부파일 구조를 최대한 재사용한다.
## 2. 현재 프로토타입에 이미 반영된 내용
| 영역 | 현재 상태 | 후속 보완 |
| --- | --- | --- |
| 피드백 목록 Status | 목록에 `Status` 컬럼이 존재함 | 실제 저장/필터/상태 변경 동작 연결 |
| 상세 처리 정보 | 상태·담당자·우선순위 선택 UI가 있음 | 프로젝트별 매니저 목록 조회 및 저장 |
| 내부 메모 | 상세창에 내부 메모 textarea가 있음 | 관리자 전용 저장, 작성자·작성일 이력화 |
| 이슈 연결 | 기존 이슈 추천, 이슈 검색 입력, 신규 이슈 생성 버튼이 있음 | 기존 이슈 검색 결과·연결 피드백 조회 및 Gitea 연동 |
| 첨부파일 | 상세창에 첨부 영역 placeholder가 있음 | 업로드/조회/다운로드 및 R2 저장 |
| 운영 지표 | 답변 대기, 이슈 연결률, 평균 처리 시간 등이 일부 표시됨 | 오늘 신규 피드백·처리 대기·평균 처리 시간 기준으로 재정의 |
| 상세 상단 메타 | ID, Created, Updated가 보임 | Issue까지 포함해 한 줄 메타 정보로 통일 |
## 3. 요구사항별 작업 목록
### A. Gitea 이슈 생성 payload 확장
- [x] Gitea 이슈 생성 시 다음 정보를 전송한다.
- 피드백 ID 및 관리자 콘솔 상세 URL
- 피드백 제목
- 피드백 description/원문
- 첨부파일 URL 목록
- 제품/프로젝트 정보
- 생성일 및 내부 이슈 피드백 수
- [ ] 첨부파일 URL은 인증이 필요한 경우에도 개발자가 접근할 수 있는 방식으로 제공한다.
- 권장: 만료시간이 있는 presigned URL 또는 Gitea 전달용 안전한 proxy URL
- 원본 파일을 Gitea 이슈 본문에 직접 삽입하지 않고 링크와 파일명·용량·MIME type을 함께 전송
- [x] Gitea API 실패·재시도·중복 생성 방지를 정의한다.
- 외부 이슈 ID, 이슈 URL, 외부 상태, 동기화 상태, 마지막 동기화 오류를 저장
- 이미 외부 이슈가 연결된 내부 이슈는 신규 생성 API를 다시 호출하지 않는다.
- 재시도 시 중복 생성 방지는 후속 보완 대상이다.
- [x] 신규 생성 이후 Gitea 이슈 번호/URL을 상세창과 목록의 Issue 항목에 표시한다.
구현 완료 범위:
- 신규 Gitea 이슈 생성 본문에 프로젝트, 내부 이슈, 연결된 피드백의 ID·상세 URL·제목·description·첨부 URL을 포함한다.
- 외부 이슈 URL·상태·동기화 상태·오류·동기화 시각을 내부 이슈에 저장한다.
- 이미 외부 이슈가 연결된 내부 이슈는 생성 API를 호출하지 않는다.
### B. 프로젝트별 매니저 담당자 배정
- [x] 피드백 상세창의 담당자 선택 목록을 현재 프로젝트에 권한이 있는 매니저 목록으로 교체한다.
- [x] `미지정` 해제 및 담당자 변경을 저장한다. (변경 이력은 후속 단계에서 추가)
- [ ] 목록에서 담당자 기준 필터를 제공한다.
- 전체
- 미지정
- 내 담당
- 특정 매니저
- [x] 담당자 목록이 비어 있거나 조회에 실패할 때 안내 문구를 제공한다. (재시도 동작은 후속 단계에서 추가)
- [x] 현재 백엔드의 `current_assignee_id`, `current_assignee_tenant_id`를 재사용하고, 프로젝트/워크스페이스 권한 검증을 적용한다.
구현 완료 범위:
- Secretary API에 프로젝트 관리자 후보 조회와 담당자 지정/해제 API를 추가했다.
- 후보자는 해당 workspace의 `PROJECT_MANAGER`와 전체 관리자이며, 다른 프로젝트 관리자는 지정할 수 없다.
- 현재 로그인한 관리자는 같은 프로젝트의 다른 관리자도 지정할 수 있다.
- 웹 피드백 상세창에서 담당자 선택 및 미지정 해제를 지원한다.
### C. 관리자 내부 메모
- [x] 피드백 상세창에 관리자 전용 내부 메모 입력·저장 영역을 제공한다.
- [x] 내부 메모는 고객에게 노출되는 답변 댓글과 분리한다.
- [x] 메모 목록에 작성자, 작성일, 수정일을 표시한다.
- [x] 메모 수정 및 삭제 권한을 정의한다.
- [x] 기존 `ticket_comments.is_internal` 구조를 재사용하고, `comment_type=INTERNAL_MEMO`로 전용 메모를 구분한다.
- 별도 테이블을 만들지 않아 기존 댓글·첨부파일·권한 구조와 호환된다.
구현 완료 범위:
- `GET/POST /api/tickets/{ticketId}/internal-memos`
- `PUT/DELETE /api/tickets/{ticketId}/internal-memos/{memoId}`
- 프로젝트 관리자 이상만 조회·작성·수정·삭제 가능
- 피드백 상세창에 내부 메모 목록과 입력·수정·삭제 UI 추가
### D. 기존 Gitea 이슈 연결 및 동일 이슈 피드백 조회
- [x] 신규 이슈 생성 외에 기존 Gitea 이슈를 검색하고 연결할 수 있게 한다.
- 이슈 번호
- 이슈 제목
- 키워드
- 이슈 상태
- [ ] 상세창에서 현재 연결된 이슈 정보와 연결 해제 동작을 제공한다.
- [x] 연결된 이슈의 동일 피드백 목록을 조회한다.
- 피드백 ID, 제목, 상태, 담당자, 생성일
- 현재 피드백을 목록에서 구분
- 프로젝트 범위 내 조회를 기본으로 함
- [ ] 한 피드백에 연결할 수 있는 외부 이슈의 cardinality를 결정한다.
- 1차 구현 권장: 피드백 1건당 대표 Gitea 이슈 1건
- 필요 시 이슈-피드백 다대다 확장 가능하도록 매핑 테이블을 고려
- [x] 기존 이슈 연결 시 신규 생성으로 처리되지 않도록 외부 이슈 ID 기반으로 저장한다.
구현 완료 범위:
- Gitea 저장소의 기존 이슈를 번호·키워드로 검색한다.
- 내부 이슈 상세창에서 검색 결과를 선택해 기존 Gitea 이슈 번호를 연결한다.
- 연결 시 `external_issue_id`에 Gitea 이슈 번호를 저장해 신규 생성과 구분한다.
- 동일 이슈 피드백 조회, 연결 해제, 연결된 이슈 메타 정보 표시는 후속 작업이다.
### E. 피드백 탭 상단 운영 지표
- [ ] 피드백 탭 상단에 다음 지표를 표시한다.
- 오늘 등록된 신규 피드백 건수
- 처리 대기 건수
- 평균 처리 시간
- [ ] 기준 시간대는 서비스 설정 또는 프로젝트 설정의 timezone을 사용하고, 기본값은 `Asia/Seoul`로 확인한다.
- [ ] 지표의 기준을 명확히 한다.
- 오늘 신규: 오늘 `created_at`이 시작 시각 이후인 피드백
- 처리 대기: 완료/종료 상태가 아닌 피드백
- 평균 처리 시간: 생성 시각부터 최초 완료 시각까지의 평균
- [ ] 데이터가 없는 경우 평균 처리 시간을 `-`로 표시한다.
- [ ] 지표와 목록의 필터 기준이 어긋나지 않도록 동일한 프로젝트·기간 범위를 사용한다.
### F. 피드백 목록 Status 표시 및 필터
- [ ] 목록 항목에 피드백 자체의 처리 상태를 표시한다.
- [ ] 상태 배지의 색상과 명칭을 통일한다.
- [ ] 최소 상태 후보:
- `신규`
- `검토 중`
- `답변 준비`
- `답변 완료`
- `종료`
- [ ] 상태 변경 시 변경 시각과 변경자를 이력에 남긴다.
- [ ] 상태 필터, 정렬, 페이지네이션이 서버 조회 기준으로 동작하도록 한다.
### G. 이슈 상태와 피드백 처리 상태 분리
- [ ] 이슈 연결 여부/외부 이슈 상태와 피드백 처리 상태를 별도 필드로 관리한다.
- `feedback_status`: 고객 피드백 처리 상태
- `issue_link_status`: 이슈 미연결/기존 이슈 연결/신규 이슈 생성 등 연결 상태
- `external_issue_status`: Gitea 이슈의 open/closed 등 외부 상태(필요한 경우)
- [ ] 고객 답변 댓글 등록 후 이슈를 생성하지 않고도 피드백을 완료 처리할 수 있게 한다.
- [ ] 이슈를 연결했다고 해서 피드백이 자동 완료되지 않도록 한다.
- [ ] 상태 전이 규칙과 완료 조건을 정의한다.
- 예: `신규 → 검토 중 → 답변 준비 → 답변 완료 → 종료`
- 예: `검토 중 → 이슈 연결`은 피드백 상태가 아니라 연결 상태에만 반영
- [ ] 기존 `status_code`가 지원 요청 lifecycle인지 피드백 처리 상태인지 확인한 후, 의미가 다르면 마이그레이션으로 분리한다.
### H. 모든 바이너리의 R2 저장
- [ ] 피드백 본문 첨부파일, 고객 댓글 이미지, 관리자 댓글 이미지 등 모든 바이너리 저장 대상을 목록화한다.
- [ ] 로컬 파일 저장을 R2 object storage 저장으로 전환한다.
- [ ] 첨부파일 메타데이터는 DB에 저장한다.
- storage provider
- bucket
- object key
- 원본 파일명
- MIME type
- 용량
- SHA-256 checksum
- 업로더 및 생성일
- [ ] 브라우저에는 직접 public URL을 노출하지 않고, 권한 검증 후 presigned download URL 또는 streaming endpoint를 제공한다.
- [ ] 기존 LOCAL 첨부파일의 처리 방식을 정한다.
- 마이그레이션 기간에는 LOCAL 조회를 유지하고 신규 파일부터 R2 저장
- 이후 기존 파일을 R2로 이관하고 DB provider/key를 갱신
- [ ] 업로드 실패 시 DB 메타데이터와 R2 object가 함께 정리되도록 보상 처리를 구현한다.
- [ ] 파일 크기, 확장자, MIME type, 이미지 첨부 제한, 바이러스 검사 여부를 기존 정책과 함께 검토한다.
- [ ] 현재 `Attachment.storage_provider` 기본값이 `LOCAL`이고 `UPLOAD_ROOT_DIR`에 파일을 쓰는 구조이므로, R2 설정값과 storage adapter를 추가한다.
### I. 상세창 최상위 글 메타 정보 한 줄 표시
- [ ] 상세창 최상단에 다음 항목을 한 줄의 메타 정보로 표시한다.
- `ID`
- `Created`
- `Updated`
- `Issue`
- [ ] Issue가 없을 때는 `미연결`로 표시한다.
- [ ] Issue가 연결된 경우 Gitea issue key와 클릭 가능한 URL을 표시한다.
- [ ] 좁은 화면에서는 줄바꿈 가능한 반응형 레이아웃으로 전환한다.
## 4. 권장 데이터/API 변경안
### 피드백/티켓
기존 `support_tickets`를 재사용할 수 있는지 먼저 확인한다. 단, 아래 필드는 의미를 명확히 분리해야 한다.
```text
feedback_status # 피드백 자체 처리 상태
issue_link_status # 외부 이슈 연결 상태
external_issue_id # Gitea issue number 또는 provider issue id
external_issue_key # 예: PROJECT-123
external_issue_url
external_issue_status
assigned_manager_id
assigned_manager_tenant_id
first_response_at
resolved_at
closed_at
```
현재 모델의 `current_assignee_id`, `current_assignee_tenant_id`, `status_code`, `issue_link_status`, `title`, `description`, `created_at`, `updated_at`를 우선 매핑 대상으로 삼는다. 실제 컬럼명을 바꾸기 전 기존 API 소비처와 마이그레이션 호환성을 확인한다.
### 내부 메모
```text
GET /api/admin/projects/{projectId}/feedbacks/{feedbackId}/internal-memos
POST /api/admin/projects/{projectId}/feedbacks/{feedbackId}/internal-memos
PUT /api/admin/projects/{projectId}/feedbacks/{feedbackId}/internal-memos/{memoId}
```
기존 ticket comment API에서 `is_internal=true`를 지원하는 경우 별도 API를 만들지 않고 관리자용 wrapper로 제공할 수 있다.
### 담당자
```text
GET /api/admin/projects/{projectId}/managers
PATCH /api/admin/projects/{projectId}/feedbacks/{feedbackId}/assignee
```
응답에는 선택 UI에 필요한 `id`, `tenantId`, `name`, `email` 또는 표시용 식별자를 포함한다.
### 이슈 검색/연결
```text
GET /api/admin/projects/{projectId}/gitea/issues/search?q=...
POST /api/admin/projects/{projectId}/feedbacks/{feedbackId}/issue-link
DELETE /api/admin/projects/{projectId}/feedbacks/{feedbackId}/issue-link
GET /api/admin/projects/{projectId}/gitea/issues/{issueId}/feedbacks
```
### 운영 지표
```text
GET /api/admin/projects/{projectId}/feedbacks/metrics?from=...&to=...
```
응답 예시:
```json
{
"todayNewCount": 0,
"pendingCount": 0,
"averageResolutionMinutes": 0,
"timezone": "Asia/Seoul"
}
```
## 5. 구현 순서
1. [x] 상태 모델 확정: 피드백 상태와 이슈 연결/외부 이슈 상태 분리
2. [x] 프로젝트별 매니저 조회 및 담당자 저장
3. [x] 내부 메모 저장/조회 및 관리자 권한 처리
4. [x] 기존 이슈 검색·연결 및 동일 이슈 피드백 조회 (연결 해제는 후속)
5. [x] Gitea 신규 이슈 생성 payload와 동기화 상태 구현
6. [x] 첨부파일 storage adapter와 R2 저장/다운로드 구현
7. [x] 운영 지표 API 및 상단 카드 구현
8. [x] 목록 Status, 상세 메타 한 줄, 상세 처리 액션 UI 연결
9. [ ] 기존 LOCAL 첨부파일 이관 및 회귀 테스트
## 6. 완료 기준
- [x] 관리자가 피드백 상세에서 현재 프로젝트의 매니저를 선택하고 저장할 수 있다.
- [x] 관리자 내부 메모가 고객 답변과 분리되어 저장·조회된다.
- [ ] 기존 Gitea 이슈를 연결하면 이슈에 연결된 다른 피드백을 조회할 수 있다.
- [ ] 신규 Gitea 이슈 생성 시 제목, description, 첨부파일 URL, 피드백 메타데이터가 전달된다.
- [ ] 이슈를 만들지 않고 고객 답변만 등록한 피드백도 별도 처리 상태로 완료할 수 있다.
- [x] 목록과 상세에 피드백 처리 상태가 표시되고 변경된다.
- [x] 피드백 탭 상단에서 오늘 신규, 처리 대기, 평균 처리 시간을 확인할 수 있다.
- [x] 신규 및 댓글 이미지 첨부파일이 R2에 저장되고 권한 있는 사용자만 다운로드할 수 있다.
- [x] 상세창 최상단에 ID/Created/Updated/Issue가 한 줄 메타 정보로 표시된다.
- [ ] 권한 없는 관리자는 담당자·내부 메모·상태·이슈 연결을 변경할 수 없다.
## 7. 확인이 필요한 결정사항
- [ ] Gitea가 프로젝트별로 동일 인스턴스인지, 프로젝트별 repository가 다른지
- [ ] Gitea API 인증 방식과 issue 생성/검색 endpoint
- [ ] Gitea 이슈 본문에서 첨부파일 URL을 접근할 수 있어야 하는 네트워크·인증 조건
- [ ] 피드백 1건에 대표 이슈 1건만 허용할지, 여러 이슈 연결을 허용할지
- [ ] `답변 완료``종료`를 별도 상태로 운영할지
- [ ] 평균 처리 시간의 완료 기준을 `답변 완료`로 할지 `종료`로 할지
- [ ] R2 bucket, endpoint, region, presigned URL 만료시간, 보존·삭제 정책
- [ ] 기존 LOCAL 첨부파일을 모두 R2로 이관할 시점
## 8. 6·7번 구현 메모
- Secretary API는 `STORAGE_PROVIDER=R2`일 때 R2 S3-compatible endpoint로 신규 피드백 첨부와 댓글 이미지 바이너리를 저장한다. 기존 `LOCAL` 첨부는 로컬 경로에서 계속 다운로드할 수 있다.
- 로컬 기본값은 `STORAGE_PROVIDER=LOCAL`이며, R2 실제 업로드를 확인하려면 로컬 `apps/secretary-api/.env``R2_ENDPOINT`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`을 별도로 설정해야 한다.
- 운영 지표는 `/api/tickets/metrics?projectId={projectId}&channelId={channelId}`에서 프로젝트·채널별로 제공하며, 오늘 신규·처리 대기·완료 건의 평균 처리 시간을 계산한다.
## 9. 참고한 기존 구현 위치
- 프로토타입 화면: `qna-platform-prototype-2.html`
- 티켓 모델: `apps/secretary-api/app/db/models.py`
- 티켓 스키마: `apps/secretary-api/app/schemas/ticket.py`
- 티켓/댓글/첨부파일 처리: `apps/secretary-api/app/services/ticket_service.py`
- 티켓 API: `apps/secretary-api/app/api/routes/tickets.py`
- 관리자 지원 콘솔 셸: `apps/web/src/features/support-portal/ui/support-operations-shell.ui.tsx`
+233
View File
@@ -0,0 +1,233 @@
# 피드백 SSOT 구조 개편 Task
> 추가 진행 메모 (2026-08-20): ABC 채널의 `feedback_status` 6단계 필드를 사용하도록 목록·상세·칸반 상태 변경을 연결했다. 댓글은 ABC `feedback_comments`로 저장하고 `is_internal`로 공개 댓글과 내부 메모를 분리했으며, 댓글 BFF는 세션 사용자·테넌트 기준 수정/삭제 권한을 검증한다. ABC 워크스페이스의 댓글 첨부파일은 Secretary로 중복 저장하지 않도록 명시적으로 차단했다.
> 추가 진행 메모 (2026-08-20): ABC 댓글 CRUD와 댓글 첨부파일 메타데이터/업로드 API를 구현했다. 첨부파일은 채널의 R2 설정을 사용해 private object로 저장하고 5분 만료 presigned URL로 조회한다. 로컬 egbim 채널에 R2 설정을 저장하고 연결 검증을 완료했다.
> 추가 진행 메모 (2026-08-20): 활성 Secretary 댓글 11건(공개 댓글 4건, 내부 메모 7건)과 댓글 첨부파일 4건을 ABC DB/R2로 이전했다. 이전 스크립트는 중복 실행 시 기존 데이터를 건너뛴다.
> 진행 메모 (2026-08-20): ABC에 저장된 관리자 필드(`IP`, `MAC_address`, `Category`)를 사용자 작성 폼의 `ip_address`, `mac_address`, `category`로 매핑했다. Category는 ABC의 선택 옵션을 그대로 표시하고, 생성·수정·상세 조회가 ABC API를 사용하도록 연결했다. Secretary fallback 제거와 인증 기반 requester 처리 등 전체 SSOT 전환은 계속 진행 중이다.
추가 진행 메모: ABC의 IP/MAC/Category 및 requester 메타데이터를 사용자 폼·단일 BFF에 연결했고, 인증 requester/tenant와 소유권 검증을 적용했다. 사용자 페이지의 테스트 상수와 support-stub 타입 의존성은 제거했다. 상태 변경은 Secretary가 아니라 ABC의 `feedback_status` 필드를 통해 처리한다.
## 1. 목표
피드백 데이터의 원본을 ABC API/DB 한 곳으로 통일한다.
관리자 콘솔과 사용자 피드백 페이지는 동일한 ABC API를 사용하고, 사용자 페이지는 데이터 저장소나 자체 fallback 데이터를 갖지 않는 API 클라이언트로 동작한다.
```text
ABC API/DB
└─ 피드백 데이터의 유일한 원본(SSOT)
관리자 콘솔
└─ ABC API를 통한 조회·관리 화면
사용자 피드백 페이지
└─ ABC API를 통한 조회·작성·수정·삭제 화면
Secretary API
└─ SSO·접근권한·운영 보조 기능
```
관리자 콘솔 화면 자체가 원본은 아니며, 콘솔이 사용하는 ABC API/DB가 원본이다.
## 2. 현재 문제
- 피드백이 Secretary DB와 ABC DB에 중복 저장됨
- 사용자 페이지의 Next.js API route에 `support-stub` fallback이 존재함
- 폼 템플릿 원본이 Secretary DB와 로컬 stub으로 분산됨
- 사용자 작성 시 테스트용 requester/tenant 값이 사용됨
- IP/MAC이 Secretary의 `extra_fields`에만 저장되고 ABC 피드백 원본에는 전달되지 않음
- 사용자 페이지와 관리자 콘솔이 서로 다른 데이터 경로를 사용할 수 있음
- Secretary 저장 성공과 ABC 저장 성공 사이의 데이터 불일치 가능성이 있음
## 3. SSOT 정책
### ABC API/DB가 보유하는 원본 데이터
- 피드백 ID
- 제목 및 내용
- 작성자 및 작성자 연락처
- 카테고리
- 비밀글 여부
- 첨부파일 및 첨부파일 메타데이터
- IP 주소 및 MAC 주소
- 피드백 중요도
- 피드백 처리 상태
- 생성일 및 수정일
- 이슈 연결 정보
- 관리자 댓글 및 내부 메모
피드백 상태와 이슈 상태는 서로 다른 필드와 생명주기로 관리한다. 이슈 상태를 피드백 상태의 원본으로 사용하지 않는다.
### Secretary API가 보유할 수 있는 데이터
- SSO 인증 및 접근권한
- 사용자·워크스페이스 접근 설정
- 운영에 필요한 보조 매핑
Secretary DB에 피드백 원본을 복제 저장하지 않는다. 불가피한 매핑이 필요한 경우 `abc_feedback_id`를 외래 식별자로 사용하고, 피드백 제목·내용·상태를 별도로 저장하지 않는다.
## 4. 단계별 Task
### Phase 1. 데이터 및 API 설계
- [x] 현재 ABC 피드백 엔티티, 관리자 필드 설정, 채널 필드 구조 확인
- [x] 피드백 원본 필드 목록 확정
- [x] IP 주소 및 MAC 주소 저장 방식 확정
- [x] 중요도 옵션과 색상 메타데이터 확정
- [x] 피드백 6단계 상태 코드 및 표시명 확정
- [x] 피드백 상태와 이슈 상태의 독립성 확인
- [x] 댓글과 내부 메모의 저장 위치 및 공개 범위 확정 (ABC `feedback_comments`; `is_internal=false/true`)
- [x] 사용자용 API와 관리자용 API의 권한 범위 정의
- [x] 피드백 CRUD API 계약서 및 응답 DTO 작성
- [x] 페이지네이션, 정렬, 검색, 상태 필터 API 규격 확정
- ABC 검색 API: `POST /api/v2/projects/:projectId/channels/:channelId/feedbacks/search`
- 요청: `page`, `limit`, `sort`, `queries`, `operator`
- 관리자 목록: ABC 네이티브 응답의 `meta` 기준 페이지 이동, 사용자 목록: BFF 응답을 10건 단위로 표시
- 상태 필터: `feedback_status` 필드 query, 이슈 상태 필터와 분리
### Phase 2. ABC 백엔드에 원본 기능 구현
- [x] 피드백 엔티티에 IP 주소 필드 추가
- [x] 피드백 엔티티에 MAC 주소 필드 추가
- [x] 피드백 엔티티에 중요도 필드 추가
- [x] 피드백 엔티티에 피드백 상태 필드 추가
- [x] 댓글·내부 메모 저장 구조 추가 (ABC `feedback_comments` 테이블 및 API)
- [x] 댓글 첨부파일 메타데이터·R2 저장/삭제/서명 URL API 추가 (`feedback_comment_attachments`)
- [x] 이슈 연결 관계와 피드백 상태를 별도 필드로 유지
- [x] 피드백 필드는 데이터베이스 migration 불필요 확인 (ABC JSON 데이터 + 채널 필드 설정 사용; 댓글은 별도 `feedback_comments` migration 적용)
- [x] 사용자 작성 API 구현
- [x] 사용자 조회 API 구현
- [x] 사용자 수정 API 구현
- [x] 사용자 삭제 API 구현
- [x] 관리자 상세 조회 API에 모든 피드백 필드 포함
- [x] 관리자 수정 API에 IP/MAC·중요도·피드백 상태 포함
- [x] API에서 requester/tenant를 인증 정보 또는 안전한 요청 정보로 처리
- [x] 첨부파일 처리와 피드백 원본의 연결 보장
- [ ] 중복 생성 방지를 위한 idempotency 또는 중복 요청 검증 추가
### Phase 3. 관리자 콘솔 개편
- [x] 관리자 피드백 목록이 ABC API만 조회하도록 통일
- [x] 관리자 피드백 상세에 IP 주소와 MAC 주소 표시
- [x] 관리자 수정 화면에서 IP 주소와 MAC 주소 표시·수정
- [x] 중요도 옵션 및 배경색 표시
- [x] 피드백 6단계 상태 및 배경색 표시
- [x] 칸반 드래그 시 ABC API의 피드백 상태만 변경
- [x] 이슈 상태는 이슈 API를 통해 별도로 변경
- [x] 피드백과 이슈 상태가 서로 덮어쓰지 않는지 확인
- [x] 댓글과 내부 메모의 저장 API 분리
- [x] 내부 메모가 외부 댓글로 복제되지 않도록 보장 (`is_internal` 분리 및 ABC 워크스페이스 Secretary 첨부댓글 경로 차단)
- [x] 목록의 페이지네이션·정렬·필터를 ABC API 기준으로 통일 (관리자 native search; 사용자 BFF의 정렬·검색·10건 페이지)
- [x] fallback 또는 임시 데이터가 표시되지 않도록 처리 (ABC 워크스페이스는 오류 응답; 비-ABC 호환 경로만 유지)
### Phase 4. 사용자 피드백 페이지 개편
- [x] 작성 페이지에서 관리자 필드 설정을 ABC API로 조회
- [x] 페이지 내부의 `support-stub` 의존성 제거
- [x] 페이지 내부의 테스트용 requester/tenant 상수 제거
- [x] 사용자 인증 정보로 작성자 정보 처리
- [x] 작성 요청을 ABC API로 직접 전달하거나 단일 BFF를 통해 전달
- [x] 제목·내용·카테고리·비밀글·IP·MAC·첨부파일을 ABC API에 저장
- [x] 사용자 본인이 작성한 피드백 목록을 ABC API로 조회
- [x] 사용자 피드백 상세를 ABC API로 조회
- [x] 사용자 수정·삭제 권한 검증
- [x] 접근 불가·존재하지 않는 피드백에 대한 오류 처리
- [x] API 오류 시 stub 데이터로 대체하지 않고 명확한 오류 표시
- [x] 작성 성공 후 ABC 피드백 ID 기준으로 상세 또는 목록 이동
### Phase 5. Secretary API 정리
- [x] ABC 매핑 워크스페이스의 Secretary 피드백 생성·복제 경로 비활성화 (비-ABC 호환 경로는 별도 유지)
- [x] ABC 매핑 워크스페이스의 `SupportTicket` 피드백 원본 중복 저장 제거 (BFF는 `abc_feedback_id`로 합성)
- [x] 기존 매핑은 `abc_feedback_id` 중심으로 사용
- [x] ABC 매핑 워크스페이스에서 Secretary는 SSO·접근권한·운영 보조 API로만 사용
- [x] ABC 매핑 워크스페이스에서 Secretary 피드백 제목·내용·상태 수정 경로 차단
- [x] ABC 매핑 워크스페이스의 기존 Secretary 기반 사용자 페이지 API route를 ABC BFF로 전환
- [ ] `support-stub.ts` fallback 제거
- [x] ABC 매핑 워크스페이스 장애 시 임의 데이터가 노출되지 않도록 오류 응답 처리
### Phase 6. 기존 데이터 이전
- [x] Secretary DB의 피드백 원본·댓글 데이터 목록 read-only inventory (로컬: 매핑 티켓 11건, 댓글/내부메모 14건)
- [x] ABC DB에 이미 존재하는 피드백과 매핑 확인 (로컬 ABC 피드백 27건, Secretary 매핑 11건)
- [x] 활성 매핑 댓글 11건 및 댓글 첨부파일 4건을 ABC DB/R2로 idempotent 이전
- [x] 이전 실행 전 Secretary/ABC 백업 승인 및 백업
- [ ] 누락된 ABC 피드백 생성 또는 이전 스크립트 작성
- [ ] IP/MAC·중요도·상태·댓글·내부 메모 이전 범위 확정 (댓글/내부메모 14건 및 댓글 첨부파일 4건의 ABC 저장 방식·중복 처리 기준 결정 필요)
- [ ] 중복 피드백 병합 기준 확정
- [x] `abc_feedback_id` 매핑 검증
- [x] 이전 전 Secretary DB와 ABC DB 백업
- [ ] staging에서 migration dry-run
- [ ] 이전 후 건수·ID·내용·상태 대조
- [ ] 이전 완료 후 Secretary 중복 데이터 읽기 차단
### Phase 7. 테스트
#### API 테스트
- [ ] 관리자 피드백 목록·상세 조회
- [ ] 사용자 피드백 작성
- [ ] 사용자 피드백 조회
- [ ] 사용자 피드백 수정
- [ ] 사용자 피드백 삭제
- [ ] IP/MAC 저장 및 조회
- [ ] 중요도 저장 및 조회
- [ ] 피드백 6단계 상태 변경
- [ ] 이슈 상태와 피드백 상태의 독립 변경
- [ ] 첨부파일 저장 및 조회
- [ ] 댓글과 내부 메모 분리
- [ ] 권한 없는 사용자의 수정·삭제 차단
- [ ] 중복 요청 방지
#### 화면 테스트
- [ ] 관리자 콘솔과 사용자 페이지의 제목·내용·상태·중요도 일치
- [ ] 작성 후 관리자 콘솔에 즉시 표시
- [ ] 관리자 수정 후 사용자 페이지에 동일하게 표시
- [ ] IP/MAC이 관리자 상세에서 표시
- [ ] 사용자에게 내부 메모가 노출되지 않음
- [ ] 10개 단위 페이지네이션 및 마지막 페이지 이동
- [ ] 리스트/칸반 조회 결과 일치
- [ ] 칸반 드래그 상태 변경 결과가 목록에도 반영
- [ ] API 장애 시 stub 데이터가 노출되지 않음
### Phase 8. 배포 및 운영 검증
- [ ] staging DB 백업
- [ ] migration 적용
- [ ] staging API 배포
- [ ] staging Web 배포
- [ ] Secretary API의 변경된 권한·매핑 검증
- [ ] 관리자 로그인 후 피드백 조회
- [ ] 일반 사용자 로그인 후 피드백 작성
- [ ] 작성 데이터가 ABC 관리자 콘솔에 표시되는지 확인
- [ ] 관리자 수정 결과가 사용자 페이지에 반영되는지 확인
- [ ] 브라우저 Console 및 Network 오류 확인
- [ ] API 응답 401/403/404/500 확인
- [ ] 데이터 건수 및 원본 ID 대조
## 5. 완료 기준
다음 조건을 모두 만족해야 SSOT 개편 완료로 판단한다.
- 피드백 원본 데이터가 ABC API/DB 한 곳에만 존재한다.
- 관리자 콘솔과 사용자 페이지가 동일한 피드백 API를 조회한다.
- 사용자 페이지에 실제 데이터용 stub/fallback이 없다.
- Secretary DB에 피드백 제목·내용·상태를 중복 저장하지 않는다.
- IP/MAC·중요도·피드백 상태·이슈 연결 정보가 관리자 콘솔에서 조회된다.
- 피드백 상태와 이슈 상태가 독립적으로 처리된다.
- 사용자 작성·수정·삭제 결과가 관리자 콘솔에 동일하게 반영된다.
- 관리자 수정 결과가 사용자 페이지에 동일하게 반영된다.
- 내부 메모가 사용자에게 노출되지 않는다.
- 데이터 이전 후 중복·누락·불일치가 없다.
## 6. 주의사항
- 운영 또는 staging DB에서 `down -v`를 실행하지 않는다.
- 기존 데이터 이전 전 DB 백업을 확보한다.
- ABC DB를 원본으로 전환하기 전 Secretary 기반 작성 API를 동시에 활성화하지 않는다.
- migration 적용 순서와 기존 데이터의 `abc_feedback_id` 매핑을 먼저 검증한다.
- ABC API 장애 시 임시 데이터를 노출하지 말고 오류 상태를 사용자에게 표시한다.