feat: V2-7 Admin 콘솔 리디자인 + 3단계 권한 + SaaS 전환 + 코드 정리
- Admin CRM: D3RO Console 스타일 전체 적용 (panelSx/tableSx/filterBtnSx) - 3단계 권한: manager/admin/super_admin (DB + Edge Functions + Frontend) - 랜딩 페이지: 1회 결제 → 월간/연간 구독 SaaS 모델 (10개 언어) - SSE 스트리밍: VoiceConversation Premium LLM 라우팅 + fallback - Supabase 클라이언트: packages/api-client 공통 추출 (browser+server) - RPC 함수 타입: 9개 정의 (admin_usage_by_feature 등) - callAdminApi 401 버그 수정 (getUser() 선행 토큰 갱신)
This commit is contained in:
parent
d0e854c255
commit
8af75a0a1e
50 changed files with 2185 additions and 950 deletions
|
|
@ -34,10 +34,26 @@
|
|||
"./usage": {
|
||||
"types": "./src/usage.ts",
|
||||
"default": "./src/usage.ts"
|
||||
},
|
||||
"./supabase-browser": {
|
||||
"types": "./src/supabase-browser.ts",
|
||||
"default": "./src/supabase-browser.ts"
|
||||
},
|
||||
"./supabase-server": {
|
||||
"types": "./src/supabase-server.ts",
|
||||
"default": "./src/supabase-server.ts"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@d3ro/core": "*",
|
||||
"@supabase/supabase-js": "^2.45.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@supabase/ssr": ">=0.10.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@supabase/ssr": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,3 +15,5 @@ export * from './auth'
|
|||
export * from './meetings'
|
||||
export * from './history'
|
||||
export * from './usage'
|
||||
export * from './supabase-browser'
|
||||
export * from './supabase-server'
|
||||
|
|
|
|||
22
packages/api-client/src/supabase-browser.ts
Normal file
22
packages/api-client/src/supabase-browser.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// packages/api-client/src/supabase-browser.ts
|
||||
// 브라우저 컴포넌트용 Supabase 클라이언트 (캐싱 싱글턴).
|
||||
// Database 제네릭 주입 (@supabase/ssr 0.10 + supabase-js 2.103 정합).
|
||||
|
||||
import { createBrowserClient } from '@supabase/ssr'
|
||||
import type { Database } from './types'
|
||||
|
||||
let cachedClient: ReturnType<typeof createBrowserClient<Database>> | null = null
|
||||
|
||||
export function getSupabaseBrowserClient(): ReturnType<typeof createBrowserClient<Database>> {
|
||||
if (cachedClient) return cachedClient
|
||||
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'https://placeholder.supabase.co'
|
||||
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? 'placeholder-anon-key'
|
||||
|
||||
cachedClient = createBrowserClient<Database>(url, key)
|
||||
return cachedClient
|
||||
}
|
||||
|
||||
export function isSupabaseConfigured(): boolean {
|
||||
return Boolean(process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY)
|
||||
}
|
||||
41
packages/api-client/src/supabase-server.ts
Normal file
41
packages/api-client/src/supabase-server.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// packages/api-client/src/supabase-server.ts
|
||||
// RSC/route handler용 Supabase 서버 클라이언트 팩토리.
|
||||
// next/headers에 직접 의존하지 않고, 호출부에서 cookieStore를 주입.
|
||||
// Database 제네릭 주입 (@supabase/ssr 0.10 + supabase-js 2.103 정합).
|
||||
|
||||
import { createServerClient, type CookieOptions } from '@supabase/ssr'
|
||||
import type { Database } from './types'
|
||||
|
||||
/** next/headers cookies()가 반환하는 객체의 최소 인터페이스 */
|
||||
export interface CookieStore {
|
||||
getAll(): Array<{ name: string; value: string }>
|
||||
set(name: string, value: string, options: CookieOptions): void
|
||||
}
|
||||
|
||||
export function createSupabaseServerClient(
|
||||
cookieStore: CookieStore,
|
||||
): ReturnType<typeof createServerClient<Database>> {
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'https://placeholder.supabase.co'
|
||||
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? 'placeholder-anon-key'
|
||||
|
||||
return createServerClient<Database>(url, key, {
|
||||
cookies: {
|
||||
getAll() {
|
||||
return cookieStore.getAll()
|
||||
},
|
||||
setAll(cookiesToSet: Array<{ name: string; value: string; options: CookieOptions }>) {
|
||||
try {
|
||||
cookiesToSet.forEach(({ name, value, options }) => {
|
||||
cookieStore.set(name, value, options)
|
||||
})
|
||||
} catch {
|
||||
// RSC에서 set은 실패하지만 route handler에서는 성공
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function isSupabaseConfiguredServer(): boolean {
|
||||
return Boolean(process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY)
|
||||
}
|
||||
|
|
@ -311,6 +311,84 @@ export type Database = {
|
|||
>
|
||||
}
|
||||
Views: Record<string, never>
|
||||
Functions: Record<string, never>
|
||||
Functions: {
|
||||
generate_invite_token: {
|
||||
Args: Record<string, never>
|
||||
Returns: string
|
||||
}
|
||||
user_team_ids: {
|
||||
Args: { uid: string }
|
||||
Returns: Array<{ team_id: string }>
|
||||
}
|
||||
user_admin_team_ids: {
|
||||
Args: { uid: string }
|
||||
Returns: Array<{ team_id: string }>
|
||||
}
|
||||
match_knowledge_chunks: {
|
||||
Args: {
|
||||
query_embedding: string
|
||||
match_count?: number
|
||||
similarity_threshold?: number
|
||||
}
|
||||
Returns: Array<{
|
||||
id: string
|
||||
document_id: string
|
||||
chunk_index: number
|
||||
content: string
|
||||
similarity: number
|
||||
}>
|
||||
}
|
||||
consume_quota: {
|
||||
Args: {
|
||||
p_user_id: string
|
||||
p_feature: string
|
||||
p_base_limit: number
|
||||
}
|
||||
Returns: Record<string, unknown>
|
||||
}
|
||||
increment_daily_usage: {
|
||||
Args: {
|
||||
p_user_id: string
|
||||
p_feature: string
|
||||
p_amount?: number
|
||||
}
|
||||
Returns: number
|
||||
}
|
||||
admin_usage_by_feature: {
|
||||
Args: {
|
||||
p_from: string
|
||||
p_to: string
|
||||
}
|
||||
Returns: Array<{
|
||||
date: string
|
||||
feature: string
|
||||
total_count: number
|
||||
unique_users: number
|
||||
}>
|
||||
}
|
||||
admin_top_users: {
|
||||
Args: {
|
||||
p_from: string
|
||||
p_to: string
|
||||
p_limit?: number
|
||||
}
|
||||
Returns: Array<{
|
||||
user_id: string
|
||||
name: string
|
||||
total_count: number
|
||||
feature_count: number
|
||||
}>
|
||||
}
|
||||
admin_dau: {
|
||||
Args: {
|
||||
p_from: string
|
||||
p_to: string
|
||||
}
|
||||
Returns: Array<{
|
||||
date: string
|
||||
active_users: number
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue