refactor(admin): CRM을 apps/admin 독립 프로젝트로 분리

- apps/web에서 admin 라우트/가드/sidebar 링크 제거
- apps/admin: 독립 Next.js 앱 (포트 3001)
  - 자체 login/unauthorized/auth callback
  - admin-sidebar: Overview/Users/Subscriptions/Usage
  - requireAdmin() 가드: profile.role='admin' 체크
- monorepo workspace에 apps/admin 등록
This commit is contained in:
윤찬 2026-04-12 20:36:24 +09:00
parent daed9f90d5
commit 46673ee941
23 changed files with 536 additions and 343 deletions

View file

@ -0,0 +1,49 @@
// apps/admin/src/lib/admin-guard.ts
// RSC용 admin 가드 — profile.role='admin' 체크
import { redirect } from 'next/navigation'
import { getSupabaseServerClient } from './supabase-server'
export interface AdminUser {
id: string
email: string | null
name: string | null
}
export async function requireAdmin(): Promise<AdminUser> {
const supabase = await getSupabaseServerClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
// role은 DB 타입에 미정의이므로 raw 캐스팅
const { data: profile } = await supabase
.from('profiles')
.select('id, name')
.eq('id', user.id)
.maybeSingle()
if (!profile) {
redirect('/login')
}
// role 별도 조회 (DB 타입에 role 컬럼 미정의)
const { data: roleData } = await supabase
.from('profiles')
.select('role' as 'id')
.eq('id', user.id)
.maybeSingle()
const role = (roleData as unknown as { role: string } | null)?.role
if (role !== 'admin') {
redirect('/unauthorized')
}
return {
id: user.id,
email: user.email ?? null,
name: (profile as { name: string | null }).name,
}
}