---
kind: work-log
title: W3 사이트 플랫폼 — 기능별 골자 카탈로그
description: W3(사이트 플랫폼 워크스트림) 5개 서브기능(W3-1 태그+검색 / W3-2 댓글·리뷰 / W3-3 포스팅 보드 / W3-4 메인 허브 / W3-5 Unity업로드 deferred)의 골자. 기능당 목적·핵심동작·결합/의존·미결질문·후속진입점. 윤곽 카탈로그 깊이 — 미결질문 해소 안 하고 남김. 당장 구현 아님, 착수 전 굳혀두는 기획 메모.
status: confirmed
source_roadmap: 2026-06-17-jam-platform-roadmap.md
source_session: 20260617-172407
confirmed_session: 20260617-174635
confirmed_at: 2026-06-18
created_at: 2026-06-17
owner: art
---

# W3 사이트 플랫폼 — 기능별 골자 카탈로그

상위 로드맵: [2026-06-17-jam-platform-roadmap.md](./2026-06-17-jam-platform-roadmap.md) §W3.

**깊이**: 윤곽 카탈로그. 기능당 목적·핵심동작·결합/의존·미결질문·후속진입점만 굳혀둔다. DDL/API 계약/시퀀스는 기능별 착수(design) 단계에서. **미결질문은 해소하지 않고 남긴다** — 골자의 목적은 "무엇을/왜 + 무엇이 미결인지"를 1페이지로 굳히는 것. 당장 구현 아님.

> W3 전부 잼 일정 독립(독립 출시 가능). 게임잼(W2)은 W3 기능의 소비자 중 하나일 뿐.

---

## 코드 현황 (착수 전 확인된 결합점)

> ⚠️ **일부 stale (2026-06-23 정정)** — 아래 #2(리뷰 테이블)·#3(Interceptor)은 W1 착지(커밋 941f9fb) + W3-2 구현 완료로 뒤집혔다. 정정·grounding: [2026-06-23-w2-w4-feature-skeletons.md](./2026-06-23-w2-w4-feature-skeletons.md) §"W3 skeletons 코드 현황 절 stale 정정". 요지: 리뷰 인프라(game_reviews+6축+stats VIEW)·권한 게이트(PermissionGate/RbacInterceptor/PermissionKeys) 모두 **실재**. 단 POST_WRITE·GAME_JAM_MANAGE 키는 enforcement 미연결(소비처 0). #1(태그)·#4·#5 는 유효.

골자의 "결합/의존" 정확도를 위해 현 코드베이스를 대조했다. file 근거:

1. **`games` 테이블** (`GamesMapper.java`) — id, user_id, name, creator_note, git_url, webgl_path, thumbnail_url, like_count, is_visible, sort_order, created_at, updated_at. **태그 컬럼 없음.** 검색은 name/creator_note ILIKE 방식만.
2. **`game_comments`** (`GameCommentsMapper.java`) — id, game_id, **nickname(자유입력)**, content, created_at, deleted_at, is_delete. 로그인 연동 없음. content 길이 제한 없음. **review 테이블·tag 테이블 모두 없음 → W3-1·W3-2 구조 전부 신규.**
3. **권한 인프라** — `UserData.role` 필드 존재, `UserController` 에 `ROLE_USER="USER"` 상수만. **Interceptor 클래스 없음** (`WebMvcConfigurer` 구현체에 `addInterceptors` 없음). W3-3 포스터 게이트가 얹힐 자리 미비 → W1 의존 강화.
4. **`index.jsp`** — 게임 카드 그리드 + 텍스트 검색(ILIKE) 단일 섹션. 잼 출품작 노출 없음.
5. **WebGL 업로드** (`GameUploadController.java`) — `/api/game-files/webgl-zip` 이미 구현. zip-slip 방어·엔트리 수 상한(8000)·압축 해제 크기 상한(512MB)·UUID 기반 `/game/{uuid}/` 배치 적용. **단 (a) 로그인 체크만, 권한 체크 없음 (b) `/game/**` 정적 핸들러가 `UploadResourceConfig.java` 에 미등록** — `/profile/**` 만 등록. → 서빙 경로 동작 미보장(QG-3, 의도/버그 미확인).

---

## 기능별 골자

### W3-1 — 태그 + 검색

- **목적**: 일반 게임 발견성 향상이 1차. 잼 용도(진행 회차 강조, 이전 회차 검색)는 부수.
- **핵심 동작**: 게임/잼에 태그를 붙이고 태그로 필터 검색(기존 텍스트 검색과 병행). 개발자(제작자) 검색 지원. 정렬은 태그 일치도 가중 × 2차 정렬키(좋아요/최신/방문수/리뷰수/점수).
- **결합/의존**:
  - C1: 신규 `tags` + 조인테이블 필요(현 코드에 태그 구조 없음). — 코드사실
  - C2: 잼 전용 태그 연결은 W2 게임잼 엔티티 선행 의존.
  - C3: 게임 태그 기본 기능은 W2 무관 독립 착수 가능. **단 정렬 일부는 의존** — 리뷰수·점수 정렬은 W3-2, 방문수 정렬은 신규 조회수 인프라.
  - C4: `GamesMapper.searchVisibleGames` ILIKE 쿼리 확장 필요. — 코드사실
  - C5: 방문수 정렬 → 신규 조회수 카운터 필요(games 에 `like_count`만, 조회수 컬럼 없음). — 코드사실
  - C6: 리뷰수·점수 정렬 → W3-2(`game_reviews`) 의존. 정렬 점수의 정확한 의미(단일 평점 vs 다축)는 W3-2 설계 종속.
  - C7: 개발자 검색 → `games.user_id` ↔ user 조인 확장.
  - 태그 테이블 구조: **통합 1테이블 + 용도 구분(게임 공용/잼 전용)** 권장. 잼 태그 요건(W2) 확정 시 분리 재검토.
- **미결질문** (합의된 방향 + 잔여):
  - Q1 태그 관리: 출시는 운영자 사전정의, 구조는 혼합(운영자 사전정의 + 사용자 생성) 지원. *잔여*: 사용자 생성 태그 개방 시점·승인 흐름.
  - Q2 제한·허용: 태그당 게임 수·게임당 태그 수 제한 없음. 허용 조건은 보안(주입·XSS sanitize) + 욕설/금칙어 필터 통과. *잔여*: 태그명 길이·허용 문자셋 구체값, 금칙어 사전 출처.
  - Q3 테이블 구조: 통합 1테이블 채택(위 결합/의존). *잔여*: 잼 태그 요건 확정 시 분리 여부.
  - Q4 정렬: 태그 미선택=최신순(기본), 태그 미선택+좋아요=전체 좋아요순, 태그 선택=태그 일치도 가중+2차키(최신/좋아요/방문수/리뷰수/점수). *잔여*: 태그 일치도 가중 계산·동률 처리는 design 단계.
  - Q5 다중 태그·개발자 검색: 다중 태그 AND/OR 지원 + 개발자(제작자) 검색 추가. *잔여*: AND/OR 노출·입력 방식은 design 단계.
- **후속 진입점**: design-advisor

### W3-2 — 댓글 / 리뷰 분리

> **구현 완료** (2026-06-18, 세션 20260618-104034). 변경 이력: [changes/2026-06-18-w3-2-comments-reviews.md](../changes/2026-06-18-w3-2-comments-reviews.md). L1 31테스트 PASS. L3 스모크·DDL 적용은 사용자 확인 필요.

- **목적**: 모든 게임 페이지 일반 기능. 잼 평가기간 게이트 없음, 잼 독립.
- **핵심 동작**: 기존 `game_comments`(닉네임 자유입력)를 로그인 사용자 연동 댓글로 전환 + 별도 리뷰(게임당 1회) 신설. 리뷰 = **별점 5점 평점 + 서술 평가**, 다축(육각형 레이더)은 후속 확장 여지. W2-6 시상이 리뷰 평점을 **단방향 집계**. 댓글 content 200자 제한 신규.
- **결합/의존**:
  - ①: `game_comments` 기존 존재 — 닉네임 자유입력, 로그인 연동 없음 → 로그인 연동 전환 시 기존 데이터 처리 결정 필요. — 코드사실
  - ②: `game_reviews` 신규 (review 테이블 없음). — 코드사실
  - ③: W2-6 시상이 리뷰 평점을 단방향 집계 → W2-3(평가 통합설계) 동결과 정합 필요. — 해석
  - ④: 댓글 content 200자 제한 신규 (현 `game_comments` content 길이 제한 없음). — 코드사실
  - ⑤: W3-1 의 리뷰수·점수 정렬이 W3-2(평점·리뷰수)에 의존 — W3-1 C6 의 역방향. — 신규
- **미결질문** (합의된 방향 + 잔여):
  - Q1 기존 닉네임 레코드 처리: **미결로 유지** (전체수준 QG-2 사안 — 골자 단계 해소 보류).
  - Q2 댓글 수정·삭제 권한: 작성자 본인 + 운영자. *잔여*: `updateGameComment` 권한 체크 신규 적용 범위는 design.
  - Q3 평점 척도: 별점 5점(초기 단일), 다축(육각형) 후속 확장 여지. *잔여*: 다축 시 축 구성·완성도/종합 분리 여부는 design.
  - Q4 리뷰 수정: 수정 가능 + 이력 보존, 노출은 "수정됨" 마커만 표시. *잔여*: 이력 열람 권한은 design.
  - Q5 W2-3 평점 집계 계약: W2-3 동결 후 정합 (W3-2 선확정 안 함).
  - Q6 *(신규)* 리뷰 단위: 업데이트 단위 분리 여부 + 나눠도 통합 평점 여부 — 미결 유지.
- **후속 진입점**: design-advisor (W2-3 진행 연계 확인 후)

### W3-3 — 포스팅 보드

- **목적**: 사이트 공지·블로그 + 외부링크 큐레이션. **별도 메뉴**, 잼 무관 독립 채널.
- **핵심 동작**: 포스터 권한자(W1 RBAC 부여)만 작성, 일반 유저 읽기만(댓글 없음). 카테고리는 운영자가 추가/수정/삭제 가능(DB 저장). 본문은 **마크다운 류 여러 서식(sanitize)**. 외부링크 큐레이션은 **OG 미리보기 포함**. 유니티블로그는 고정 카테고리가 아니라 **외부 피드(레퍼런스) 감시 — 새 글 게시 감지 후 알림** 형태로 별도 취급.
- **결합/의존**:
  - C1: **W1 선행 필요** — `UserData.role` 존재하나 Interceptor 미구현. 권한 게이트 인프라가 W1 에서 구현돼야 함. — 코드사실
  - C2: `posts`/`board_posts` 신규.
  - C3: `RecruitPostsMapper`/`RecruitController` 유사 게시판 패턴 참고 가능. — 코드사실(참고)
  - C4: *(신규)* 외부 fetch 인프라 — OG 미리보기 + 유니티블로그 피드 감시 → SSRF 방어·캐싱·실패처리·폴링 필요. — 신규
- **미결질문** (합의된 방향 + 잔여):
  - Q1 착수 시점: **W1 완료 후 착수** (임시 role 직접 체크 안 함). → 전체수준 QG-1 의 W3-3 부분 결정. *(W3-5 권한 부분은 여전히 미결)*
  - Q2 카테고리 저장: DB 저장 (운영자 추가/수정/삭제 가능).
  - Q3 외부링크: OG 미리보기 포함. *잔여*: SSRF 방어·캐싱·실패처리 방식은 design.
  - Q4 본문 형식: 마크다운 류 여러 서식(sanitize). *잔여*: 허용 서식 범위·sanitize 정책은 design.
  - Q5 댓글 부착: **붙이지 않음** (읽기 전용 채널). → W3-2 결합 없음.
  - Q6 *(신규)* 유니티블로그 외부 피드 감시: 새 글 게시 감지 → 알림 형태. *미결*: 확인 방식(RSS/폴링)·알림 표면·갱신 주기.
- **후속 진입점**: design-advisor (W1 진행 확인 선행)

### W3-4 — 메인페이지 (게임 허브)

- **목적**: `index.jsp` 를 게임 허브로 확장 + 진행 중 잼 안내. 포스팅은 별도 메뉴(메인 통합 아님).
- **핵심 동작**: 평소엔 현 게임 카드 그리드 전체 노출(현 정렬 유지). 진행 중 잼 있을 때만 **검색창 아래에 "게임잼 진행 중" 안내 + 신규 출품작 강조**, 클릭 시 **게임잼 태그 검색(W3-1)으로 이동**. (별도 잼 섹션 그리드 아님.) 페이지네이션/무한스크롤 도입.
- **결합/의존**:
  - C1: 현 `index.jsp` 는 `GamesMapper.getVisibleGames()`/`searchVisibleGames()` 단일 소스. — 코드사실
  - C2: 잼 노출(검색창 아래 안내·신규작 강조·클릭 라우팅) = **W2(잼 엔티티·진행 상태) + W3-1(잼 태그 검색)** 의존. — 신규/해석
  - C3: 일반 게임 허브 개선(페이지네이션·그리드)은 W2/W3-1 무관 독립.
- **미결질문** (합의된 방향 + 잔여):
  - Q1 잼 노출 조건: 진행 중 잼 있을 때만.
  - Q2 노출 형태: 별도 그리드 섹션 대신 검색창 아래 안내 배너 + 신규작 강조 → 클릭 시 잼 태그 검색. 평소 전체 게임 그대로. *(별도 그리드 없어 중복 노출 문제 해소)*
  - Q3 정렬·추천: 현 정렬 유지(`sort_order` ASC + `created_at` DESC). 인기순은 후속.
  - Q4 페이지네이션: 도입. *잔여*: 페이지네이션 vs 무한스크롤 택은 design.
  - Q5 단계 착수: **단계 착수 가능** — 일반 허브(페이지네이션·그리드) 먼저, 잼 안내는 W2+W3-1 후.
- **후속 진입점**: design-advisor

### W3-5 — Unity WebGL 빌드 업로드 자동화 ⏸ deferred

> 골자 아님 — **선행 조사 스텁**. 조사 완료 후 골자/설계 진입.

- **목적**: 일반 게임 호스팅 자동화. 현 수동 호스팅 대체. 게임잼 무관 인프라.
- **현황**: 업로드 로직 부분 구현 존재(위 코드현황 5). zip-slip·크기상한·UUID 배치 됨. **권한 체크 없음 + `/game/**` 정적 핸들러 미등록.**
- **선행 조사 항목**:
  - zip-slip 방어 충분성 — 현 `target.startsWith(targetDir)` 외 심볼릭 링크·인코딩 우회 케이스.
  - WebGL 빌드 포맷 검증 범위 — index.html 외 필수 파일(Build/*.data, *.wasm, *.js) 존재 검증.
  - 업로드 크기/타입 — zip 원본 크기 상한, Content-Type MIME+확장자 이중 체크 여부.
  - 저장 경로 boundary — `app.upload.game-storage-path` 외부 주입 시 경계 검증.
  - `/game/**` 정적 핸들러 미등록 — 서빙 동작 미보장(QG-3).
  - 업로드 권한 게이트 — 현 로그인만, 권한 제한 없음.
  - UUID 재사용 시 기존 레코드 덮어쓰기/삭제 정책.
- **착수 전 합의(이번 세션)**: 조사 항목 7개 그대로 유지. 업로드 권한 게이트는 QG-1 미결로 유지(조사 단계 확정). QG-3(`/game/**` 핸들러)는 코드 확인을 조사 단계로 미룸.
- **후속 진입점**: research-advisor → (조사 후) design-advisor

---

## 워크스트림 결합 요약

| 기능 | 결합 대상 | 결합 방식 | 독립 출시 |
|---|---|---|---|
| W3-1 태그+검색 | W2(잼 태그) + W3-2(리뷰수·점수 정렬) + 조회수 인프라(방문수 정렬) | 잼 태그·정렬 일부 의존, 게임 태그 코어는 독립 | 부분 (게임 태그 코어 먼저) |
| W3-2 댓글/리뷰 | W2-6(시상 집계) + W2-3(평점 계약 정합) + W3-1(정렬이 평점·리뷰수 소비) | 리뷰 평점을 W2-6 단방향 집계, W3-1 정렬이 역참조 | 가능 (댓글·리뷰 코어 독립, 평점 집계 계약은 W2-3 동결 후) |
| W3-3 포스팅 보드 | W1(포스터 권한/인터셉터) + 외부 fetch 인프라(OG·유니티블로그 피드) | 권한 게이트 의존, W1 완료 후 착수(임시 체크 안 함) | W1 선행 |
| W3-4 메인 허브 | W2(잼 엔티티·진행 상태) + W3-1(잼 태그 검색 라우팅) | 잼 안내·신규작 강조→잼 태그 검색 의존, 일반 허브는 독립 | 부분 (일반 허브 먼저, 잼 안내는 W2+W3-1 후) |
| W3-5 WebGL 자동화 | 없음 (일반 인프라) | 독립 | deferred (조사 선행) |

---

## 착수 순서 제언 (단정 아님 — 이번 세션 합의)

> 의존 사슬: W3-2(평점 공급) → W3-1(정렬이 평점 소비) → W3-4(잼 안내가 잼 태그 검색으로 라우팅). W3-3·W3-5 는 W1 권한 게이트 선행.

1. **W3-2 댓글/리뷰** — 기존 `game_comments` 확장, 코어 완전 독립, 착수 비용 최소. 다른 기능의 평점 공급원. 평점 집계 계약만 W2-3 동결 후 정합.
2. **W3-1 태그+검색** — W3-2 뒤에 오면 리뷰수·점수 정렬까지 완성. 게임 태그 코어는 독립, 정렬 일부는 W3-2·조회수 인프라 의존.
3. **W3-4 메인 허브 (일반 부분 먼저)** — 페이지네이션·그리드 개선은 독립 선착수. 잼 안내(검색창 아래 배너→잼 태그 검색)는 W2+W3-1 후.
4. **W3-3 포스팅** — W1 Interceptor 선행(임시 체크 안 함 확정). OG·유니티블로그 외부 fetch 인프라 동반.
5. **W3-5 WebGL 자동화** — deferred, 선행 조사 완료 후.

---

## 전체 수준 미결질문 (착수 전 확정 필요)

- **QG-1**: W1 인터셉터 완료 시점과 권한 게이트 착수. **W3-3 은 W1 완료 후 착수(임시 role 직접 체크 안 함)로 확정.** W3-5 업로드 권한은 미결 유지(조사 단계 확정). *잔여*: W1 지연 시 W3-5 임시 체크 허용 여부.
- **QG-2**: W3-2 기존 닉네임 자유입력 레코드 처리 — 마이그레이션/유지/레거시 표시. **미결 유지 확정**(골자 단계 해소 보류).
- **QG-3**: ~~W3-5 `/game/**` 정적 핸들러 미등록이 의도인지 버그인지~~ → **해소(2026-06-23 조사)**: `/game/**` 서빙은 `ResourceHandler` 가 아니라 전용 컨트롤러 `GameAssetController`(`@GetMapping("/game/{gameUuid}/**")`, boundary·Content-Type·CSP·br/gz 인코딩 처리)가 담당 — **정상 동작(의도된 설계, 버그 아님)**. `UploadResourceConfig` 가 `/profile/**` 만 등록한 것은 맞으나 WebGL 은 컨트롤러 경로라 무관. 근거: `.atp/work-session/20260623-104307/research/W3-5-upload-research.md`. (W3-5 실 결함은 zip-slip 심링크 구멍·업로드 권한게이트 전무로 재확인됨.)

---

## 다음 단계

골자 합의 완료(이번 세션 — 5기능 + 착수 순서). 각 기능을 실제 착수할 때 위 "후속 진입점" advisor 로 진입한다. 착수 전 해당 기능의 *잔여* 미결 + 관련 QG 를 먼저 확정한다. **착수 1순위 = W3-2 로 확정.**
