Files
ai-dev-portal/ai-dev-portal-prd.md
Hyemin Lee e174fccef2 docs: PRD·CLAUDE 보강과 PLANS 마무리 (Phase 6)
- PRD: 반응 2종(heart/star)·공개여부·lang·대댓글·라우트·다크모드/검색/토스트·role 확정 반영
- CLAUDE: 구조 맵(app.js·helpers.js·_project_detail·hearts)·role 인증 모델 갱신
- 부팅 스모크 테스트(/healthz) 통과, 실 DB 기능 점검만 잔여

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 17:31:47 +09:00

13 KiB

AI DEV 포털 — 기획서 (PRD)

사내 개발의 시작점. 한 곳에서 개발 도구로 이동하고, 만든 프로젝트를 공유·소통하는 경량 포털. Backstage를 걷어내고 직접 만든 가벼운 사이트로 노선 변경. Stack: Node(Express + EJS) + Postgres + Keycloak SSO. Kubero로 배포.


1. 한 줄 정의

직원이 사번 계정 한 번의 로그인(SSO) 으로 사내 개발 도구(Coder/Gitea/Kubero 등)에 이동하고, 자기가 만든 프로젝트를 동료에게 공유 → 좋아요 → 댓글로 소통하는 내부 개발 포털.


2. 왜 Backstage를 버리는가

항목 Backstage 직접 만든 경량 포털
무게 무겁고 과함 (플러그인/카탈로그/엔티티 모델) 필요한 화면 2~3개
학습/운영 비용 높음 낮음 (Express+EJS)
커스터마이징 프레임워크에 종속 100% 자유
우리가 실제 쓰는 기능 링크 모음 + 공유 피드 정확히 그 두 가지

우리가 원하는 건 "예쁜 링크 허브 + 가벼운 소셜 피드". Backstage는 오버킬.


3. 화면 구조 (3 페이지)

3.1 로그인 (/login)

  • 미인증 진입 시 중간 화면 없이 곧장 Keycloak 로그인 폼으로 리다이렉트 (이미 구현됨).
  • OIDC discovery 실패 시에만 안내 페이지 폴백.

3.2 대시보드 (/) — 메인

로그인 후 보게 되는 단일 페이지. 위→아래로 3개 영역.

┌─────────────────────────────────────────────┐
│  [헤더] AI DEV   ·····      사번님 ▾ (로그아웃) │
├─────────────────────────────────────────────┤
│  ① 개발 도구 (Tools)                          │
│     - 모두에게: Coder / Gitea / Kubero / Harbor│
│     - 관리자만: OpenEverest / MinIO /          │
│                 Keycloak / LiteLLM             │
│     클릭 시 SSO 자동로그인 URL로 새 탭          │
├─────────────────────────────────────────────┤
│  ② 내 프로젝트 (My Projects)                   │
│     - 내가 공유한 것만. 공유 피드와 시각적으로  │
│       확연히 구분 (배경/테두리/섹션 헤더 다름)  │
│     - [+ 새 프로젝트 공유] 버튼                 │
├─────────────────────────────────────────────┤
│  ③ 공유 프로젝트 피드 (Shared Projects)        │
│     - 나 + 남이 공유한 전체 (최신순/인기순)     │
│     - 카드: 제목·설명·태그·작성자·❤️ 수·💬 수   │
│     - 카드 클릭 → 프로젝트 상세 (3.3)           │
│     - 카드의 "바로가기" → 배포 URL 새 탭        │
└─────────────────────────────────────────────┘

핵심 UX 결정: ②와 ③의 구분

  • ②는 "내 작업 공간"의 느낌 — 좌측 액센트 바, 옅은 배경 톤, "내 프로젝트" 섹션 라벨.
  • ③은 "공개 피드"의 느낌 — 카드 그리드, 작성자 아바타 노출, 좋아요/댓글 카운트 전면.
  • 같은 카드 컴포넌트를 쓰되 컨테이너 스타일과 배지로 분리. (관리자가 아니어도 둘은 항상 분리.)

3.3 프로젝트 상세 (/projects/:id)

  • 제목 / 설명 / 태그 / 작성자 / 작성일
  • 바로가기 버튼 (배포 URL — 새 탭)
  • 좋아요 토글 (❤️ + 카운트)
  • 댓글 + 대댓글(1뎁스까지)
    • 댓글: 작성자, 시각, 본문, [답글] 버튼
    • 대댓글: 부모 댓글 아래 들여쓰기. 대댓글에는 [답글] 버튼 없음 (1뎁스 고정)
    • 본인 댓글은 삭제 가능

4. 권한 모델 (Role)

Keycloak에서 내려주는 role(또는 group)로 분기. preferred_username = 사번.

역할 볼 수 있는 도구 비고
일반 사용자 Coder, Gitea, Kubero, Harbor 개발 핵심
관리자(admin) 위 + OpenEverest, MinIO, Keycloak, LiteLLM 인프라/운영 도구까지
  • 구현: 세션의 rolesadmin 포함 여부로 sites.js의 각 항목 adminOnly: true 필터링.
  • 일반 사용자에게 관리자 전용 카드는 렌더링 자체를 안 함 (숨김, disabled 아님).
  • 프로젝트 공유/좋아요/댓글은 역할 무관 모두 가능.
// sites.js 형태 (예시)
export const sites = [
  { key:'coder',  name:'Coder',  desc:'클라우드 IDE',     url: SSO_START.coder,  icon:'coder',  group:'dev' },
  { key:'gitea',  name:'Gitea',  desc:'Git 저장소',       url: SSO_START.gitea,  icon:'gitea',  group:'dev' },
  { key:'kubero', name:'Kubero', desc:'배포(PaaS)',       url: KUBERO_URL,       icon:'kubero', group:'dev' },
  { key:'harbor', name:'Harbor', desc:'컨테이너 레지스트리', url: HARBOR_URL,     icon:'harbor', group:'dev' },
  { key:'openeverest', name:'OpenEverest', desc:'DB 콘솔', url: OE_URL,    icon:'db',  group:'infra', adminOnly:true },
  { key:'minio',  name:'MinIO',  desc:'오브젝트 스토리지', url: MINIO_URL,  icon:'minio',  group:'infra', adminOnly:true },
  { key:'keycloak', name:'Keycloak', desc:'계정/SSO 관리', url: KC_URL,    icon:'key', group:'infra', adminOnly:true },
  { key:'litellm', name:'LiteLLM', desc:'LLM 게이트웨이',  url: LITELLM_URL, icon:'ai', group:'infra', adminOnly:true },
];

5. 배포 URL 연동 — Kubero에서 어떻게 받아오나

네 질문: "공유된 프로젝트 링크를 새 탭으로 열려면 Kubero에서 URL을 받아와야 하나?"

결론: Kubero API를 실시간으로 조회하지 말고, 공유 시점에 URL을 직접 저장한다.

왜 실시간 조회가 아닌가

  • Kubero는 빌드 후 설정된 도메인(configured domain) 으로 앱을 노출함. 앱마다 도메인은 이미 정해져 있음 (<app>.apps.bokdev.in 같은 패턴).
  • Kubero v3에 API가 있지만, 포털이 매 렌더링마다 Kubero API를 때리면: ① 토큰/권한 관리 부담 ② Kubero 다운 시 포털 피드도 깨짐 ③ 폐쇄망에서 불필요한 결합도 증가.
  • 공유 프로젝트의 URL은 거의 안 바뀜. 실시간성이 필요 없는 데이터.

권장 방식 (단순)

프로젝트 공유 폼에서 사용자가 URL을 직접 입력(또는 선택):

  • repo_url — Gitea 저장소 주소
  • app_url — 배포된 앱 주소 (예: https://myapp.apps.bokdev.in) — 이게 "바로가기"가 여는 링크
  • 둘 다 선택 입력. app_url이 없으면 "바로가기" 버튼은 숨김.

(선택) 편의 기능 — 나중에

공유 폼에서 "내 Kubero 앱 목록 불러오기" 버튼 → Kubero API로 사용자의 파이프라인/앱과 그 도메인을 가져와 드롭다운 제공. URL 오타 방지용. MVP에는 불필요, 직접 입력으로 충분.


6. SSO 자동로그인 (도구 카드 클릭 시)

이미 적용된 패턴 유지:

  • Coder/Gitea처럼 OIDC를 지원하는 도구는 OIDC 시작 URL(자동로그인 진입점)로 링크 → 사용자가 도구에서 다시 로그인 안 함.
  • 카드에 "SSO" 배지로 자동로그인 가능함을 표시.
  • 모든 도구 링크는 target="_blank" + rel="noopener" 로 새 탭.

7. 데이터 모델 (Postgres, schema portal)

-- 사용자: Keycloak가 진실의 원천. 포털은 표시에 필요한 최소만 캐시.
users (
  id            text primary key,   -- preferred_username (사번)
  display_name  text,
  is_admin      boolean default false,
  created_at    timestamptz default now()
)

projects (
  id          bigserial primary key,
  owner_id    text not null references users(id),
  title       text not null,
  description text,
  repo_url    text,                 -- Gitea 등
  app_url     text,                 -- 배포 URL (바로가기 대상)
  tags        text,                 -- 콤마 구분(실제 구현은 text). 표시 시 split.
  is_public   boolean default true, -- 공개(피드 노출) / 비공개(나만)
  lang        text,                 -- 대표 언어(태그에서 추론, 카드 언어 점)
  created_at  timestamptz default now(),
  updated_at  timestamptz default now()
)

-- 반응은 2종(디자인 확정): ❤️ 좋아요(hearts) + ⭐ 즐겨찾기/Star(stars). 각 1인 1회.
hearts (
  project_id  bigint references projects(id) on delete cascade,
  user_id     text   references users(id),
  created_at  timestamptz default now(),
  primary key (project_id, user_id)   -- 1인 1좋아요
)

stars (
  project_id  bigint references projects(id) on delete cascade,
  user_id     text   references users(id),
  created_at  timestamptz default now(),
  primary key (project_id, user_id)   -- 1인 1즐겨찾기
)

comments (
  id          bigserial primary key,
  project_id  bigint not null references projects(id) on delete cascade,
  user_id     text   not null references users(id),
  parent_id   bigint references comments(id) on delete cascade,  -- null이면 최상위
  body        text   not null,
  created_at  timestamptz default now()
)
-- 대댓글 1뎁스 제약: parent_id가 가리키는 댓글의 parent_id는 반드시 null
-- (앱 레벨에서 검증. 대댓글에 또 답글 달기 차단)

조회 쿼리 포인트:

  • 피드 카드의 ❤️ 수 / 수 / 💬 수는 count 서브쿼리로(db.jsPROJECT_SELECT).
  • "내 좋아요/즐겨찾기 여부"는 hearts/stars(project_id, 현재 사용자) 존재 여부(EXISTS).
  • 목록은 공개 프로젝트 + 본인 비공개만 반환(남의 비공개 비노출).
  • ⚠️ 실제 스키마는 사번(sabun)을 PK/FK 로 쓰고 users.is_admin 은 두지 않는다(관리자 여부는 매 로그인 시 Keycloak role 로 판별). 컬럼·타입의 단일 진실은 schema.sql.

8. 라우트 (API)

Method Path 설명 권한
GET / 대시보드 (도구 + 내 프로젝트 + 피드). ?open=:id/?toast=/?egg= 신호 로그인
GET /projects/:id 프로젝트 상세(단독 페이지·모달 폴백). 비공개는 본인만 로그인
POST /projects 프로젝트 공유 생성 (visibility·lang 포함) 로그인
POST /projects/:id/heart ❤️ 좋아요 토글 (AJAX 면 {on,count}) 로그인
POST /projects/:id/star 즐겨찾기 토글 (AJAX 면 {on,count}) 로그인
POST /projects/:id/visibility 공개/비공개 전환 본인
POST /projects/:id/delete 내 프로젝트 삭제 (form POST) 본인
POST /projects/:id/comments 댓글/대댓글 작성 (parent_id 옵션, 1뎁스 검증) 로그인
POST /comments/:id/delete 내 댓글 삭제 (form POST) 본인
GET /auth/callback OIDC 콜백 -
GET /logout 로그아웃 -

반응/댓글은 점진적 향상: JS 없으면 폼 POST→리다이렉트, JS 있으면 fetch 로 즉시 반영. 삭제는 메서드 제약(HTML 폼) 때문에 POST .../delete 로 처리(논리적으로는 DELETE).


9. 비기능 / 운영

  • 배포: Kubero, NodeJS buildpack (npm installnode index.js). Dockerfile 불필요.
  • Env: BASE_URL, DATABASE_URL, OIDC_*, SESSION_SECRET, PORTAL_BRAND, ADMIN_ROLE, 각 도구 URL.
  • DB: OpenEverest Postgres의 appdb, schema portal, role portal_app.
  • Issuer: https://keycloak.bokdev.in/realms/bokdev.
  • 브랜드명: PORTAL_BRAND 환경변수로 교체 가능 (AI DEV 기본).
  • 폐쇄망: 외부 폰트/CDN 의존 최소화. 폰트는 시스템 폰트 폴백(Pretendard → Apple SD Gothic/Malgun Gothic)으로 동작. CDN 불가 환경에선 public/ 에 Pretendard woff2 로컬 호스팅 권장(선택). 아이콘은 인라인 SVG(Octicon)·이모지로 외부 의존 없음.
  • 클라이언트 부가기능(프런트 전용, 서버 무관): 라이트/다크 테마 토글(localStorage), 헤더 검색, 토스트 알림, 이스터에그(로고 흔들기·👍🚀 날리기·LGTM 스탬프·콘솔 아트).

10. MVP 범위 / 나중

MVP (지금 — 구현 완료)

  • 3화면(대시보드+상세 모달/단독 페이지), 역할 분기(관리자 도구 숨김), 프로젝트 공유, 공개/비공개, ❤️ 좋아요 + 즐겨찾기, 댓글+대댓글(1뎁스), SSO 링크, 바로가기(직접 입력 URL), 검색·정렬(최신/인기)·즐겨찾기 필터(클라이언트), 라이트/다크 테마.

나중에 (선택)

  • Kubero API로 앱 목록 자동 불러오기 (URL 입력 편의)
  • 서버측 검색/태그 필터·정렬(현재는 클라이언트 처리, 새로고침 시 초기화)
  • 댓글 알림, 멘션
  • 프로젝트 썸네일/스크린샷
  • 내 프로젝트 수정(edit) (현재는 삭제·가시성 전환만)

11. 열린 질문 / 결정

  1. 관리자/일반 구분을 role 로 줄지 group 으로 줄지확정: Keycloak realm role (realm_access.rolesADMIN_ROLE(기본 admin) 포함 여부). 운영 중 group 으로 바꾸려면 auth.js 수정.
  2. "내 프로젝트" 수정(edit) → MVP 제외(삭제·가시성 전환만). 추후 과제.
  3. 좋아요/댓글에 익명성은 없음(사번 기반) — 사내 문화상 OK 가정.