From 5fa83933e434e944d6d3ec0ecc36d52660c9c5b3 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Wed, 2 Sep 2026 17:29:02 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[fix]=20postgres-init:=20=EA=B5=AC=EA=B8=80?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=BB=AC=EB=9F=BC=EC=9D=98=20?= =?UTF-8?q?=EC=9D=B8=EB=8D=B1=EC=8A=A4=EB=A5=BC=20ALTER=20=EB=92=A4?= =?UTF-8?q?=EB=A1=9C=20=E2=80=94=20=EA=B8=B0=EC=A1=B4=20DB=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=EA=B0=80=20?= =?UTF-8?q?=EB=A9=88=EC=B6=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 실측: dev DB(web4ai_db)에 재적용하니 인덱스 절에서 끊겼다. ERROR: column "provider_uid" does not exist 파일 구조가 CREATE TABLE → 인덱스 → 기존 DB 보정(ALTER) 순이라, 새 DB 에서는 CREATE TABLE 이 컬럼을 만들어 주지만 **기존 DB 에서는 컬럼이 맨 끝 ALTER 로 생긴다.** 인덱스를 위에 두면 ON_ERROR_STOP 에서 나머지 보정까지 통째로 안 돈다 — 새 DB 에서만 테스트하면 안 보이는 종류다. - uq_users_provider_uid 를 ALTER 섹션 끝으로 옮기고, 왜 거기 있어야 하는지 주석으로 못 박았다 dev DB 재적용으로 확인: ALTER 4건 + CREATE INDEX 통과, company.users 에 provider/provider_uid 생성 및 password NULL 허용 확인. --- postgres-init/init-data/init.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/postgres-init/init-data/init.sql b/postgres-init/init-data/init.sql index 6545250..13b373c 100644 --- a/postgres-init/init-data/init.sql +++ b/postgres-init/init-data/init.sql @@ -358,8 +358,6 @@ CREATE INDEX IF NOT EXISTS idx_users_company_id ON company.users (company_id); -- 소프트 삭제를 쓰므로 자연키 유니크는 부분 인덱스(deleted = FALSE)로 건다. CREATE UNIQUE INDEX IF NOT EXISTS uq_users_id ON company.users (id) WHERE deleted = FALSE; --- 같은 구글 계정으로 두 번 가입되지 않게. provider 를 키에 넣어 수단이 늘어도 이 인덱스가 그대로 쓰인다. -CREATE UNIQUE INDEX IF NOT EXISTS uq_users_provider_uid ON company.users (provider, provider_uid) WHERE deleted = FALSE AND provider_uid IS NOT NULL; CREATE UNIQUE INDEX IF NOT EXISTS uq_companies_biz_number ON company.companies (business_number) WHERE deleted = FALSE AND business_number IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_companies_code ON company.companies (code) WHERE deleted = FALSE; @@ -441,3 +439,7 @@ ALTER TABLE company.users ADD COLUMN IF NOT EXISTS provider_uid VARCHAR(255) NULL; ALTER TABLE company.users ALTER COLUMN id TYPE VARCHAR(64); ALTER TABLE company.users ALTER COLUMN password DROP NOT NULL; +-- ★ 이 인덱스는 위 인덱스 절이 아니라 **여기** 있어야 한다. 기존 DB 에서는 컬럼이 ALTER 로 +-- 생기므로, 인덱스를 먼저 만들면 "column provider_uid does not exist" 로 스크립트가 통째로 멈춘다. +CREATE UNIQUE INDEX IF NOT EXISTS uq_users_provider_uid ON company.users (provider, provider_uid) + WHERE deleted = FALSE AND provider_uid IS NOT NULL; From 4d6802b4f2d241a40874cb2a3de66ab424721d94 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Wed, 2 Sep 2026 17:29:02 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[feat]=20solution/frontend:=20=EC=97=90?= =?UTF-8?q?=EB=94=94=ED=84=B0=EC=97=90=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=91=9C=EC=8B=9C=20=E2=80=94=20?= =?UTF-8?q?6=EB=8B=A8=EA=B3=84=EC=97=94=20=EC=8B=A0=EC=9B=90=EB=8F=84=20?= =?UTF-8?q?=EB=82=98=EA=B0=80=EB=8A=94=20=EA=B8=B8=EB=8F=84=20=EC=97=86?= =?UTF-8?q?=EC=97=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 위저드(1~5단계)는 AppShell 사이드바가 사용자와 [로그아웃]을 들고 있는데, 에디터(6단계)는 전체 화면이라 AppShell 을 안 쓴다. 그래서 편집 화면에 들어가는 순간 **누구로 로그인했는지도, 나가는 방법도 화면에서 사라졌다.** - BuilderPage: 에디터 상단 바 오른쪽에 사용자 · 상호와 [로그아웃] 추가 - stores/auth.userLabel: 이름 → 이메일 → 아이디 순. 구글 계정의 로그인 아이디는 google_ 라 그대로 보이면 안 된다. AppShell 도 같은 규칙을 쓰게 바꿨다 (기존 `name ?? id` 는 이름이 빈 문자열이면 그대로 통과시켰다) 브라우저 확인: 가입 → 로그인 → 사이드바 '김사장 · 달빛스테이', 에디터 상단 바 동일 표시, [로그아웃] 클릭 시 RequireAuth 가 /login 으로 되돌림. tsc·eslint·vite build 통과. --- .../src/components/layout/AppShell.tsx | 4 +- solution/frontend/src/pages/BuilderPage.tsx | 69 +++++++++++++------ solution/frontend/src/stores/auth.ts | 8 +++ 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/solution/frontend/src/components/layout/AppShell.tsx b/solution/frontend/src/components/layout/AppShell.tsx index b78b597..d1d943c 100644 --- a/solution/frontend/src/components/layout/AppShell.tsx +++ b/solution/frontend/src/components/layout/AppShell.tsx @@ -2,7 +2,7 @@ import type {ComponentType, ReactNode} from 'react'; import {Link, NavLink, useLocation} from 'react-router'; import {LayoutGrid, LogOut, Search, Wand2} from 'lucide-react'; import {cn} from '@/lib/utils'; -import {useAuthStore} from '@/stores/auth'; +import {userLabel, useAuthStore} from '@/stores/auth'; export type NavItem = { to: string; @@ -60,7 +60,7 @@ export function AppShell({children, nav = OWNER_NAV}: {children: ReactNode; nav?
- {user?.name ?? user?.id} + {user ? userLabel(user) : ''} {user?.companyName ? ` · ${user.companyName}` : ''}
+ + )} +
diff --git a/solution/frontend/src/stores/auth.ts b/solution/frontend/src/stores/auth.ts index 50f291c..82fad43 100644 --- a/solution/frontend/src/stores/auth.ts +++ b/solution/frontend/src/stores/auth.ts @@ -31,6 +31,14 @@ export function toAuthUser(res: ResMe): AuthUser { }; } +/** + * 화면에 띄울 이름. 구글 계정의 로그인 아이디는 `google_` 라 그대로 보이면 안 된다 — + * 이름 → 이메일 순으로 떨어뜨리고 아이디는 마지막이다. + */ +export function userLabel(user: AuthUser): string { + return user.name || user.email || user.id; +} + interface AuthState { user: AuthUser | null; /** 부팅 시 저장된 토큰을 확인하기 전까지 true. 가드가 이 동안 리다이렉트를 미룬다. */ From e1423418ae71481a73fbcc05f3866100a5d63ed8 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Wed, 2 Sep 2026 21:38:43 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[fix]=20solution/frontend:=20=EB=B9=84?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=83=81=ED=83=9C=EC=9D=98=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=20=E2=80=94=20=EB=88=8C?= =?UTF-8?q?=EB=9F=AC=EB=8F=84=20=EC=95=84=EB=AC=B4=20=EC=9D=BC=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20[=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83]=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 위저드는 로그인 없이 열린다(관문은 에디터 진입이다). 그런데 사이드바는 로그인 여부와 무관하게 [로그아웃]만 그렸다 — 로그인한 적 없는 사람에게는 **이름이 빈 줄로 나오고, 버튼을 눌러도 지울 세션이 없어 아무 일도 일어나지 않았다.** "유저 정보가 어디에도 안 보인다"가 이것이다. - 비로그인: '로그인하지 않았습니다' + [로그인] 링크 - 로그인: 이름 · 상호 + [로그아웃]. 로그아웃은 스토어만 비우면 화면이 그대로라 눌러도 아무 일이 없는 것처럼 보인다 — /login 으로 보낸다 브라우저 확인: 비로그인 위저드에서 안내와 [로그인] 노출 → 로그인 후 '김사장 · 달빛스테이' → [로그아웃] 클릭 시 /login 이동. tsc·eslint·vite build 통과. --- .../src/components/layout/AppShell.tsx | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/solution/frontend/src/components/layout/AppShell.tsx b/solution/frontend/src/components/layout/AppShell.tsx index d1d943c..61a1fe7 100644 --- a/solution/frontend/src/components/layout/AppShell.tsx +++ b/solution/frontend/src/components/layout/AppShell.tsx @@ -1,6 +1,6 @@ import type {ComponentType, ReactNode} from 'react'; -import {Link, NavLink, useLocation} from 'react-router'; -import {LayoutGrid, LogOut, Search, Wand2} from 'lucide-react'; +import {Link, NavLink, useLocation, useNavigate} from 'react-router'; +import {LayoutGrid, LogIn, LogOut, Search, Wand2} from 'lucide-react'; import {cn} from '@/lib/utils'; import {userLabel, useAuthStore} from '@/stores/auth'; @@ -30,6 +30,7 @@ export function AppShell({children, nav = OWNER_NAV}: {children: ReactNode; nav? const user = useAuthStore((s) => s.user); const signOut = useAuthStore((s) => s.signOut); const location = useLocation(); + const navigate = useNavigate(); return (
@@ -58,19 +59,43 @@ export function AppShell({children, nav = OWNER_NAV}: {children: ReactNode; nav? ))} + {/* ★ 위저드는 로그인 없이도 열린다(관문은 에디터 진입이다) — 그래서 이 자리는 + **비로그인 상태를 반드시 그려야 한다.** 예전엔 이름이 빈 줄로 나오고 [로그아웃]만 + 남아서, 로그인한 적 없는 사람이 눌러도 아무 일이 안 일어났다(지울 세션이 없다). */}
- {user ? userLabel(user) : ''} - {user?.companyName ? ` · ${user.companyName}` : ''} + {user ? ( + <> + {userLabel(user)} + {user.companyName ? ` · ${user.companyName}` : ''} + + ) : ( + 로그인하지 않았습니다 + )}
- + {user ? ( + + ) : ( + + + 로그인 + + )}