npm package: @god-beom/fsot-vite
Description: Overlay collaboration tool for React frontends — per-page kanban & real-time chat
Latest version: 0.1.2

# fsot - Frontend Source Overlay Tool

---

## fwagger — 화면 데이터 명세 (VM Swagger)

프론트엔드 라우트별로 사용하는 데이터 타입과 훅을 자동으로 수집해서 Swagger처럼 보여주는 기능입니다.

### 🚀 빠른 시작

**라우트에 매핑된 컴포넌트 옆에 `*.VM.ts` 파일을 만들면 끝입니다.**

```
src/pages/cost/
  CostDashboardPage.tsx       ← 라우터에 등록된 컴포넌트
  CostDashboardPage.VM.ts     ← 이 파일만 만들면 fsot가 알아서 수집
```

```bash
# 스펙 생성
cd apps/your-app
npx fsot swagger
# → fsot.swagger.json 생성됨
```

개발 서버를 켜면 오버레이에서 실시간으로 확인할 수 있습니다.

---

### VM 파일 작성 규칙

서버와 주고받는 데이터 타입과 React Query 훅만 작성합니다.
**UI 전용 타입(탭 구조, 정렬 상태 등)은 포함하지 않습니다.**

```ts
// CostDashboardPage.VM.ts

import { useQuery } from '@tanstack/react-query';
import { fetchMonthlyCost, type MonthlyCostResponse } from '../../api/costApi';

// ─── Types ───────────────────────────────────
export interface CloudCostVM {
  cloud: 'aws' | 'azure' | 'gcp';
  amount: number;
  currency: string;
}

export interface MonthlyCostVM {
  periodLabel: string;
  total: number;
  clouds: CloudCostVM[];
}

// ─── Mapper ──────────────────────────────────
function toMonthlyCostVM(dto: MonthlyCostResponse): MonthlyCostVM {
  return dto;
}

// ─── Hook ────────────────────────────────────
export function useMonthlyCost() {
  return useQuery({
    queryKey: ['cost', 'monthly'],
    queryFn: fetchMonthlyCost,
    select: toMonthlyCostVM,
  });
}
```

**파일명 태그 자동 추론:**

| 파일명 패턴 | 태그 |
|-------------|------|
| `*Page.VM.ts` | `PAGE` |
| `*Tab.VM.ts` | `TAB` |
| `*Modal.VM.ts` | `MODAL` |
| 그 외 | `COMPONENT` |

---

### fsot가 VM을 수집하는 방법

#### 1단계 — 라우터 파일 파싱

`vite.config.ts`에 지정한 라우터 파일(`src/routes/index.tsx`)을 읽어서 경로-컴포넌트 매핑을 추출합니다.

```tsx
// 이런 라우터 정의에서
{ path: '/cost/dashboard', element: <CostDashboardPage /> }

// 이 매핑을 만듭니다
'/cost/dashboard' → CostDashboardPage.tsx
```

#### 2단계 — import 트리 재귀 추적

각 페이지 컴포넌트에서 시작해 import된 파일들을 따라가며 `*.VM.ts` 파일을 찾습니다.

```
CostDashboardPage.tsx
  └─ import CostSummaryCard        → CostSummaryCard.VM.ts ✅ 수집
  └─ import BudgetSettingsModal    → BudgetSettingsModal.VM.ts ✅ 수집
  └─ import SomeChart              → VM 없음 → skip
```

단, **다른 라우트 컴포넌트를 만나면 탐색을 멈춥니다.** 탭 레이아웃처럼 자식 라우트 컴포넌트를 import하는 경우 VM이 중복 수집되는 것을 방지합니다.

#### 3단계 — VM 파일 AST 파싱

수집된 `*.VM.ts` 파일에서 `interface`와 `export function useXxx()` 를 파싱해 스펙을 생성합니다.

#### 4단계 — LayoutPage 자동 제외

자식 라우트가 있는 LayoutPage는 항상 첫 번째 탭으로 리다이렉트되므로 manifest에서 자동으로 제외됩니다.

```
/asset/scheduler          ← LayoutPage → 자동 제외
/asset/scheduler/schedule ← TabPage   → 수집 ✅
/asset/scheduler/history  ← TabPage   → 수집 ✅
```

**따라서 LayoutPage에는 VM 파일이 필요 없습니다.** 탭 전환 로직은 LayoutPage 컴포넌트 안에 직접 작성합니다.

---

### 탭 구조 권장 패턴

```
asset/scheduler/
  SchedulerLayoutPage.tsx          ← 탭 UI + 전환 로직 (VM 없음)
  SchedulePageTab.tsx              ← 라우트: /asset/scheduler/schedule
  SchedulePageTab.VM.ts            ← Types + Mapper + Hook ✅
  ScheduleHistoryPageTab.tsx       ← 라우트: /asset/scheduler/history
  ScheduleHistoryPageTab.VM.ts     ← Types + Mapper + Hook ✅
```

**한 파일에 모두:** `TabPage.VM.ts` = Types + Mapper + Hook (완결)

---

### vite.config.ts 설정

```ts
import { fsot } from '@prime/fsot/vite';

export default defineConfig({
  plugins: [
    fsot({
      projectId: 'my-project',
      serverUrl: 'https://...',
      vm: {
        router: 'react-router',
        routerFile: 'src/routes/index.tsx',
        include: 'src/**/*.VM.ts',
      },
    }),
  ],
});
```

---

## 인증 구조

Supabase Auth를 사용하지 않고, `fsot_users` 테이블 + bcrypt + JWT로 직접 인증합니다.

```
클라이언트 → POST /api/auth/setup, /api/auth/login → 서버(bcrypt 검증) → JWT 반환
클라이언트 → Authorization: Bearer <JWT> → 서버(JWT 검증 + fsot_users 조회)
```

- 비밀번호 해싱: 서버에서만 (bcrypt)
- 세션: JWT (localStorage `fsot_token`), 유효기간 7일
- DB: `fsot_users` 테이블 (RLS OFF)
- Supabase Auth 완전 미사용 → bit/hito와 충돌 없음

### 서버 API

| 엔드포인트 | 설명 |
|---|---|
| GET /api/auth/setup-status | 초기 설정 여부 확인 |
| POST /api/auth/setup | 관리자 계정 생성 (첫 사용 시) |
| POST /api/auth/login | 로그인 → JWT 반환 |
| GET /api/auth/me | 현재 사용자 정보 (JWT 필요) |
| GET /api/admin/users | 사용자 목록 (admin+) |
| POST /api/admin/users | 사용자 생성 (admin+) |
| PUT /api/admin/users | 사용자 수정 (admin+) |
| DELETE /api/admin/users | 사용자 삭제 (admin+) |

### 역할 (role)

| role | 설명 |
|---|---|
| god | 최고 관리자, 프로젝트 간 로그인 가능 |
| hostAdmin | 호스트앱 관리자 |
| admin | 일반 관리자 |
| user | 일반 사용자 |

## 보안 TODO (미해결)

### fsot 전용 테이블 RLS OFF 상태 — 보안 취약

현재 아래 테이블들의 RLS가 OFF되어 있습니다:

- `kanban_cards`
- `kanban_columns`
- `chat_messages`
- `page_completions`

**원인**: Supabase Auth 제거 후 기존 RLS 정책(`auth.uid()` 기반)이 동작하지 않아 임시로 RLS를 비활성화.

**위험**: 클라이언트(`useKanban.ts`, `useChat.ts`)가 Supabase anon key로 직접 DB에 접근하고 있어, anon key를 아는 누구나 데이터를 조작할 수 있음.

**해결 방법**: 클라이언트의 Supabase 직접 접근을 제거하고, 모든 CRUD를 fsot 서버 API를 경유하도록 변경.

```
현재 (위험): 클라이언트 → (anon key) → Supabase (RLS OFF)
목표 (안전): 클라이언트 → (JWT) → fsot 서버 → (service_role) → Supabase
```

수정 대상 파일:
- `src/hooks/useKanban.ts` — 칸반 CRUD
- `src/hooks/useChat.ts` — 채팅 메시지 CRUD
- `src/overlay/KanbanPanel.tsx` — page_completions 직접 접근
- 서버에 대응하는 API 라우트 추가 필요

## 실행

```bash
# 서버 (포트 4000)
cd apps/fsot/server && npx tsx src/index.ts

# 환경변수: server/.env
SUPABASE_URL=https://kgejfsxantavmzwdukmq.supabase.co
SUPABASE_SECRET_KEY=<service_role_key>
JWT_SECRET=<optional, 기본값 있음>
PORT=4000
```

## Supabase 프로젝트

sotfront (`kgejfsxantavmzwdukmq`) — bit, hito와 공유. fsot 전용 테이블만 사용.