SaaS [9] — 실증 A/B/C/D 전부 통과, 로컬→클라우드 push 최초 성공(pushed=1). ## Bug 4: 로컬 nanoid PK vs Supabase UUID PK 불일치 - 증상: Push history failed: invalid input syntax for type uuid: "fvy6bIzr..." - 원인: 로컬 drizzle schema는 text PK + nanoid() 생성, Supabase는 uuid PK. 빅뱅 사이클 내내 push가 한 번도 성공한 적 없었음 (지난 pushed=0은 데이터 0건이라서). - 픽스: 로컬을 UUID로 통일 (근본 해결, 땜질 금지). 14개 서비스 20곳 nanoid() → crypto.randomUUID() 일괄 교체. nanoid 의존성 + electron.vite.config exclude 제거. drizzle schema는 text PK 그대로 유지 (SQLite는 UUID 문자열 저장 가능). ## Bug 5: supabase_realtime publication 누락 - 증상: 로그인 직후 Realtime 채널 상태: TIMED_OUT - 원인: initial_schema.sql이 transcripts 테이블만 publication에 추가. 데스크톱이 구독하는 meetings/history/dictionary는 누락 → postgres_changes 흐르지 않음. - 픽스: 20260411000002_realtime_publication.sql 신규. pg_publication_tables 카탈로그 체크 + 조건부 ADD TABLE (meetings/meeting_memos/ meeting_documents/history/dictionary 5개). supabase db push 적용. ## Bug 6: persistSession:false에서 realtime.setAuth 자동 전파 안 됨 (부분 픽스) - 픽스: CloudSyncService.startRealtime()에 client.realtime.setAuth(access_token) 명시 호출 (채널 구성 이전). - ⚠️ Bug 5+6 적용 후에도 Realtime 여전히 TIMED_OUT. 후속 조사 필요. 블로커 아님 — 주기 pull + Phase 3.3 auto push로 최종 일관성 유지. ## 실증 결과 - A 세션 자동 복원: Restored session for yunchan8804@gmail.com → DB 재오픈 - B push 경로: HistoryService created 56a767ac-... → Sync complete pushed=1 errors=0 - C 로그아웃 복귀: Realtime 종료 → users/_local/d3ro.db 복귀 → local mode - D 재로그인 복원: 실증 A의 restore 경로와 동일, 같은 uuid DB 파일 보존 - E 웹 크로스 디바이스: Phase 3.3 이후로 지연 (Realtime 이슈 별건) 검증: desktop tsc --noEmit ✅, dev 재기동 ✅, push 최초 성공 ✅
86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
import { resolve } from 'path'
|
|
import { defineConfig, externalizeDepsPlugin, loadEnv } from 'electron-vite'
|
|
import react from '@vitejs/plugin-react'
|
|
|
|
const sharedAlias = {
|
|
'@d3ro/core': resolve(__dirname, '../../packages/core/src'),
|
|
'@d3ro/ui': resolve(__dirname, '../../packages/ui/src'),
|
|
'@d3ro/i18n': resolve(__dirname, '../../packages/i18n/src/index.tsx')
|
|
}
|
|
|
|
const workspaceExclude = ['@d3ro/core', '@d3ro/ui', '@d3ro/i18n']
|
|
|
|
// SaaS 데스크톱 클라이언트: Supabase 연결 정보를 빌드 타임에 박는다.
|
|
// `apps/desktop/.env` (committed shared defaults) + `.env.local` (gitignored, 개인/비공개 키)
|
|
// 우선순위는 vite/electron-vite의 dotenv 로드 규칙: .env.local > .env.[mode] > .env
|
|
// 환경 변수에 값이 없으면 빈 문자열로 박혀, 런타임에 ConfigService가 fallback(설정 UI 입력)으로 동작.
|
|
function loadSupabaseEnv(mode: string): { url: string; anonKey: string } {
|
|
const env = loadEnv(mode, process.cwd(), 'D3RO_')
|
|
return {
|
|
url: env.D3RO_SUPABASE_URL ?? '',
|
|
anonKey: env.D3RO_SUPABASE_ANON_KEY ?? ''
|
|
}
|
|
}
|
|
|
|
export default defineConfig(({ mode }) => {
|
|
const supabase = loadSupabaseEnv(mode)
|
|
const supabaseDefine = {
|
|
'process.env.D3RO_SUPABASE_URL': JSON.stringify(supabase.url),
|
|
'process.env.D3RO_SUPABASE_ANON_KEY': JSON.stringify(supabase.anonKey)
|
|
}
|
|
|
|
return {
|
|
main: {
|
|
plugins: [
|
|
externalizeDepsPlugin({
|
|
exclude: ['electron-store', ...workspaceExclude]
|
|
})
|
|
],
|
|
resolve: { alias: sharedAlias },
|
|
define: supabaseDefine
|
|
},
|
|
preload: {
|
|
plugins: [externalizeDepsPlugin({ exclude: workspaceExclude })],
|
|
build: {
|
|
rollupOptions: {
|
|
input: {
|
|
index: resolve(__dirname, 'src/preload/index.ts'),
|
|
popup: resolve(__dirname, 'src/preload/popup.ts')
|
|
}
|
|
}
|
|
},
|
|
resolve: { alias: sharedAlias }
|
|
},
|
|
renderer: {
|
|
build: {
|
|
rollupOptions: {
|
|
input: {
|
|
index: resolve(__dirname, 'src/renderer/index.html'),
|
|
'popups/recording-tip': resolve(
|
|
__dirname,
|
|
'src/renderer/popups/recording-tip/index.html'
|
|
),
|
|
'popups/result-popup': resolve(
|
|
__dirname,
|
|
'src/renderer/popups/result-popup/index.html'
|
|
),
|
|
'popups/history-popup': resolve(
|
|
__dirname,
|
|
'src/renderer/popups/history-popup/index.html'
|
|
),
|
|
'popups/command-popup': resolve(
|
|
__dirname,
|
|
'src/renderer/popups/command-popup/index.html'
|
|
),
|
|
'popups/caption-overlay': resolve(
|
|
__dirname,
|
|
'src/renderer/popups/caption-overlay/index.html'
|
|
)
|
|
}
|
|
}
|
|
},
|
|
resolve: { alias: sharedAlias },
|
|
plugins: [react()]
|
|
}
|
|
}
|
|
})
|