refactor: 인증 시스템을 Supabase Auth로 통합 (#45) - #47
Conversation
- 이메일/비번 회원가입·로그인을 자체 JWT+Prisma에서 Supabase Auth로 이전
- me·logout을 Supabase 세션 기반으로 교체
- 중복된 자체 카카오 OAuth 라우트 제거 (로그인 화면은 Supabase 카카오 OAuth 사용)
- profiles 확장 마이그레이션 추가 (가입 부가정보 트리거 반영)
- 제거: /api/auth/kakao/*, lib/auth/{jwt,kakao,password}.ts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 46 minutes and 24 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
WalkthroughSupabase Auth 통합을 위해 프로필 테이블 스키마 확장, 관리자 클라이언트 인프라 추가, 그리고 로그인·로그아웃·프로필·회원가입 엔드포인트를 JWT+Prisma 기반에서 Supabase Auth 기반으로 마이그레이션합니다. 레거시 JWT·카카오·bcrypt 코드는 제거됩니다. ChangesSupabase Auth 통합
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related issues
Possibly related PRs
Suggested labels
핵심 검토 포인트1. 마이그레이션 완결성
2. 데이터 모델 정합성
3. 역호환성 & 레거시 코드
4. 보안 관점
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
package.json과 lock 파일 불일치로 CI의 `npm ci`가 실패하던 문제 해결. @testing-library/dom 등 누락된 peer/전이 의존성을 lock에 반영. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/api/auth/login/route.ts`:
- Around line 39-46: The admin profile lookup ignores the possible error from
admin.from("profiles").select(...).maybeSingle(), causing DB/RLS failures to be
masked as auth 401s; update the code in the createAdminClient /
admin.from("profiles").select("email").eq("nickname", loginId).maybeSingle()
flow to check the returned error, log it (including error details), and either
throw or return a 500 response instead of treating email as null; ensure the
variable email is only set when no error and profile exists, and propagate or
rethrow the error so calling code doesn't incorrectly convert server errors into
authentication failures.
- Around line 55-66: The login flow currently only checks error and data.user
after calling createClient() and supabase.auth.signInWithPassword, but must also
verify data.session to fail when email confirmation prevents session creation;
update the conditional in route.ts (the signInWithPassword handling block) to
treat error || !data.user || !data.session as authentication failure and return
the existing NextResponse.json with AUTH_FAIL_MESSAGE, and add a
unit/integration test that mocks signInWithPassword returning data.session =
null to assert the 401/failed response.
In `@src/app/api/auth/logout/route.ts`:
- Around line 6-13: The POST handler calls createClient() and invokes
supabase.auth.signOut() but ignores its result; update the POST function to
capture the signOut response, check the returned error, and only return 200 when
signOut succeeded or when the error indicates "no active session"/"invalid
session" (treat as success), otherwise return a 500 JSON response with the error
message; reference the POST function, the createClient() call, and
supabase.auth.signOut() so you locate and change the response logic to
conditionally use NextResponse.json with status 200 or 500 based on the error.
In `@src/app/api/auth/me/route.ts`:
- Around line 18-23: The Supabase query result is ignoring the returned error
and only destructuring data (profile); update the block after calling
createClient() and the supabase.from("profiles").select(...).maybeSingle() call
to also destructure and check the returned error, and if error is present log
the error (include error.message and details) and return a 500 HTTP response
instead of continuing with an undefined profile; ensure the fix references
createClient, supabase, .from("profiles").select(...).maybeSingle(), and the
profile variable so the query error path cleanly logs and returns 500.
In `@src/app/api/auth/signup/route.ts`:
- Around line 85-94: The handler currently returns success with a user payload
even when data.user is null (variable user), which can cause NPEs on the client;
change the response logic in signup/route.ts to check if data.user is null and,
if so, return a clear response like success: true/false with message "이메일 인증이
필요합니다" (or similar) and omit user.id access (or return user: null) so clients
know to wait for email confirmation; update the NextResponse.json call that
currently builds { success, message, user: user ? { id: user.id, email:
user.email, nickname } : null } to branch on user === null and return the
explicit confirmation-required message and appropriate status.
- Around line 71-82: Replace the fragile regex-only status decision in the
signup error handler by first casting/inspecting the thrown error for a
structured status (e.g., check err?.status) and only fall back to the current
regex on err?.message; extract this logic into a small helper (e.g.,
determineSignupErrorStatus) referenced from the route's error branch that
returns 409 for duplicates and 400 otherwise, keep the existing localized
messages and NextResponse.json call using the computed status, and add a Vitest
unit test asserting that structured 409 errors produce 409 and that messages
matching duplicate patterns also produce 409 while all other errors produce 400.
In `@src/lib/supabase/admin.ts`:
- Around line 12-33: The admin Supabase client lacks the Database generic so
query types aren't checked; generate the Supabase types (e.g., run `supabase gen
types typescript` to produce a Database type file) and then update
createAdminClient: import the generated Database type (e.g., Database from
'`@/types/supabase`'), change the cached variable and function signature to use
SupabaseClient<Database>, and call createClient<Database>(...) so the returned
client and all queries like from("profiles").select(...) are type-safe.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1e28a062-4021-4f6e-bd44-b408f86b5aec
📒 Files selected for processing (12)
.env.examplesrc/app/api/auth/kakao/callback/route.tssrc/app/api/auth/kakao/login/route.tssrc/app/api/auth/login/route.tssrc/app/api/auth/logout/route.tssrc/app/api/auth/me/route.tssrc/app/api/auth/signup/route.tssrc/lib/auth/jwt.tssrc/lib/auth/kakao.tssrc/lib/auth/password.tssrc/lib/supabase/admin.tssupabase/migrations/20260607000000_profiles_auth_fields.sql
💤 Files with no reviewable changes (5)
- src/lib/auth/kakao.ts
- src/lib/auth/password.ts
- src/lib/auth/jwt.ts
- src/app/api/auth/kakao/login/route.ts
- src/app/api/auth/kakao/callback/route.ts
| export async function POST() { | ||
| const res = NextResponse.json( | ||
| const supabase = await createClient(); | ||
| await supabase.auth.signOut(); | ||
|
|
||
| return NextResponse.json( | ||
| { success: true, message: "로그아웃되었습니다." }, | ||
| { status: 200 }, | ||
| ); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
signOut() 에러 핸들링 누락
supabase.auth.signOut()이 실패해도(네트워크 오류, 세션 이미 만료 등) 항상 200 성공 응답을 반환한다.
Why: 로그아웃 실패 시에도 성공으로 처리되면 클라이언트가 상태 동기화에 혼란을 겪을 수 있다. 다만, 세션이 이미 없는 경우는 성공으로 처리해도 무방.
How: signOut() 결과의 error를 확인하고, 네트워크 오류 등 실제 장애 시 500 반환 권장. 단, 세션 없음 에러는 200 처리 가능.
🔧 수정 제안 (선택적)
export async function POST() {
const supabase = await createClient();
- await supabase.auth.signOut();
+ const { error } = await supabase.auth.signOut();
+
+ // 세션 없음 에러는 이미 로그아웃된 상태이므로 성공 처리
+ if (error && !error.message.includes("session")) {
+ console.error("[auth.logout] signOut 실패:", error);
+ return NextResponse.json(
+ { success: false, message: "로그아웃 처리 중 오류가 발생했습니다." },
+ { status: 500 },
+ );
+ }
return NextResponse.json(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/api/auth/logout/route.ts` around lines 6 - 13, The POST handler calls
createClient() and invokes supabase.auth.signOut() but ignores its result;
update the POST function to capture the signOut response, check the returned
error, and only return 200 when signOut succeeded or when the error indicates
"no active session"/"invalid session" (treat as success), otherwise return a 500
JSON response with the error message; reference the POST function, the
createClient() call, and supabase.auth.signOut() so you locate and change the
response logic to conditionally use NextResponse.json with status 200 or 500
based on the error.
| const user = data.user; | ||
|
|
||
| return NextResponse.json( | ||
| { | ||
| success: true, | ||
| message: "회원가입이 완료되었습니다.", | ||
| user: user ? { id: user.id, email: user.email, nickname } : null, | ||
| }, | ||
| { status: 201 }, | ||
| ); |
There was a problem hiding this comment.
Email confirmation 활성화 시 data.user가 null일 수 있음
Supabase Auth에서 email confirmation이 필수인 경우 signUp 성공 시에도 data.user가 null로 반환될 수 있다. 현재 코드는 user: null을 그대로 응답에 포함하는데, 이는 의도된 동작인지 명확하지 않다.
Why: 클라이언트가 user.id 접근 시 NPE 발생 가능.
How: data.user가 null인 경우 "이메일 인증이 필요합니다" 등의 명시적 메시지 반환 권장.
🔧 수정 제안
const user = data.user;
+ // Email confirmation 활성화 시 user가 null일 수 있음
+ if (!user) {
+ return NextResponse.json(
+ {
+ success: true,
+ message: "회원가입이 완료되었습니다. 이메일 인증을 진행해주세요.",
+ user: null,
+ },
+ { status: 201 },
+ );
+ }
+
return NextResponse.json(
{
success: true,
message: "회원가입이 완료되었습니다.",
- user: user ? { id: user.id, email: user.email, nickname } : null,
+ user: { id: user.id, email: user.email, nickname },
},
{ status: 201 },
);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/api/auth/signup/route.ts` around lines 85 - 94, The handler currently
returns success with a user payload even when data.user is null (variable user),
which can cause NPEs on the client; change the response logic in signup/route.ts
to check if data.user is null and, if so, return a clear response like success:
true/false with message "이메일 인증이 필요합니다" (or similar) and omit user.id access (or
return user: null) so clients know to wait for email confirmation; update the
NextResponse.json call that currently builds { success, message, user: user ? {
id: user.id, email: user.email, nickname } : null } to branch on user === null
and return the explicit confirmation-required message and appropriate status.
There was a problem hiding this comment.
확인했습니다. 의도된 동작이라 현행 유지합니다. 이메일 확인(Confirm email) 활성 시 data.user가 null이어도 가입 자체는 접수된 것이므로, 현재 코드는 user ? { ... } : null로 그레이스풀하게 처리하고 success(201)를 반환합니다. 즉 null도 정상 분기입니다. 추후 '확인 메일 발송됨' 같은 명시적 안내 메시지는 #45 후속(이메일 확인 정책 확정 시)에서 다루겠습니다.
There was a problem hiding this comment.
의도된 동작임을 확인했습니다. user: null 분기는 "가입 접수, 이메일 인증 대기" 상태를 나타내는 정상 응답으로 처리됩니다. 이메일 확인 정책 확정 후 명시적 메시지 추가 시 이 스레드를 참고하면 됩니다.
✏️ Learnings added
Learnt from: kokkumong
Repo: Siul49/moim PR: 47
File: src/app/api/auth/signup/route.ts:87-96
Timestamp: 2026-06-07T07:42:13.883Z
Learning: `src/app/api/auth/signup/route.ts`의 `POST` 핸들러에서 `supabase.auth.signUp` 성공 후 `data.user`가 `null`일 수 있는 것은 의도된 동작이다. Supabase email confirmation 활성화 시 가입은 접수되지만 `user`가 null로 반환되며, 현재 코드는 `user ? { id, email, nickname } : null`로 그레이스풀하게 처리하고 HTTP 201을 반환한다. "확인 메일 발송됨" 같은 명시적 안내 메시지는 이메일 확인 정책 확정 후 별도 PR(`#45` 후속)에서 다룰 예정이다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| import { createClient, type SupabaseClient } from "@supabase/supabase-js"; | ||
|
|
||
| let cached: SupabaseClient | null = null; | ||
|
|
||
| export function createAdminClient(): SupabaseClient { | ||
| if (cached) return cached; | ||
|
|
||
| const url = process.env.NEXT_PUBLIC_SUPABASE_URL; | ||
| const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; | ||
|
|
||
| if (!url || !serviceRoleKey) { | ||
| throw new Error( | ||
| "Supabase 관리자 클라이언트 설정이 누락되었습니다 (NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY).", | ||
| ); | ||
| } | ||
|
|
||
| cached = createClient(url, serviceRoleKey, { | ||
| auth: { autoRefreshToken: false, persistSession: false }, | ||
| }); | ||
|
|
||
| return cached; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Database 타입 정의 파일 존재 여부 확인
fd -t f "supabase" src/types --exec echo "Found: {}"
# 다른 Supabase 클라이언트에서 Database 타입 사용 여부 확인
rg -n "SupabaseClient<Database>" --type ts
rg -n "createClient<Database>" --type tsRepository: Siul49/moim
Length of output: 37
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 관련 타입 파일 존재 확인
echo "== supabase 관련 types 파일 찾기 =="
fd -t f "*supabase*" src/types --exec ls -la {}
echo "== Database 타입 정의/참조 찾기 =="
rg -n "export type Database|type Database|interface Database" src/types --type ts || true
rg -n "SupabaseClient<Database>|createClient<Database>|from\(\"profiles\"\)|profiles\"\\)" src --type ts || true
# 2) admin client 파일 실제 내용 일부 확인
echo "== src/lib/supabase/admin.ts =="
sed -n '1,120p' src/lib/supabase/admin.ts
# 3) 해당 경로의 login route 실제 코드 확인(있다면)
if [ -f src/app/api/auth/login/route.ts ]; then
echo "== src/app/api/auth/login/route.ts =="
sed -n '1,200p' src/app/api/auth/login/route.ts
else
echo "route.ts not found at src/app/api/auth/login/route.ts"
fiRepository: Siul49/moim
Length of output: 393
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 관련 타입 파일 존재 확인
echo "== supabase 관련 types 파일 찾기 =="
fd -t f "*supabase*" src/types --exec ls -la {}
echo "== Database 타입 정의/참조 찾기 =="
rg -n "export type Database|type Database|interface Database" src/types --type ts || true
rg -n "SupabaseClient<Database>|createClient<Database>|from\(\"profiles\"\)|profiles\"\\)" src --type ts || true
# 2) admin client 파일 실제 내용 일부 확인
echo "== src/lib/supabase/admin.ts =="
sed -n '1,120p' src/lib/supabase/admin.ts
# 3) 해당 경로의 login route 실제 코드 확인(있다면)
if [ -f src/app/api/auth/login/route.ts ]; then
echo "== src/app/api/auth/login/route.ts =="
sed -n '1,200p' src/app/api/auth/login/route.ts
else
echo "route.ts not found at src/app/api/auth/login/route.ts"
fiRepository: Siul49/moim
Length of output: 393
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== supabase 관련 types 파일 찾기 (fixed-strings) =="
fd -t f --fixed-strings "supabase" src/types --exec ls -la {} || true
echo "== Database 타입 정의/참조 찾기 =="
rg -n --hidden --no-ignore-vcs "type Database|interface Database|export type Database" src/types --type ts || true
rg -n --hidden --no-ignore-vcs "SupabaseClient<Database>|createClient<Database>" src --type ts || true
echo "== admin client 파일 실제 내용 =="
sed -n '1,200p' src/lib/supabase/admin.ts
echo "== profiles 테이블 from() 사용처 찾기 =="
rg -n --hidden --no-ignore-vcs "from\\(\"profiles\"\\)|from\\('profiles'\\)" src --type ts || true
echo "== src/app/api/auth/login/route.ts 존재/내용 =="
if [ -f src/app/api/auth/login/route.ts ]; then
sed -n '1,240p' src/app/api/auth/login/route.ts
else
echo "route.ts not found at src/app/api/auth/login/route.ts"
fi
echo "== supabase-admin 사용처(대략) 찾기 =="
rg -n --hidden --no-ignore-vcs "createAdminClient\\(" src --type ts || trueRepository: Siul49/moim
Length of output: 3521
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== supabase 관련 types 파일 찾기 (fixed-strings) =="
fd -t f --fixed-strings "supabase" src/types --exec ls -la {} || true
echo "== Database 타입 정의/참조 찾기 =="
rg -n --hidden --no-ignore-vcs "type Database|interface Database|export type Database" src/types --type ts || true
rg -n --hidden --no-ignore-vcs "SupabaseClient<Database>|createClient<Database>" src --type ts || true
echo "== admin client 파일 실제 내용 =="
sed -n '1,200p' src/lib/supabase/admin.ts
echo "== profiles 테이블 from() 사용처 찾기 =="
rg -n --hidden --no-ignore-vcs "from\\(\"profiles\"\\)|from\\('profiles'\\)" src --type ts || true
echo "== src/app/api/auth/login/route.ts 존재/내용 =="
if [ -f src/app/api/auth/login/route.ts ]; then
sed -n '1,240p' src/app/api/auth/login/route.ts
else
echo "route.ts not found at src/app/api/auth/login/route.ts"
fi
echo "== supabase-admin 사용처(대략) 찾기 =="
rg -n --hidden --no-ignore-vcs "createAdminClient\\(" src --type ts || trueRepository: Siul49/moim
Length of output: 3521
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== src/lib/supabase/server.ts =="
sed -n '1,200p' src/lib/supabase/server.ts
echo "== src/lib/supabase/client.ts =="
sed -n '1,200p' src/lib/supabase/client.ts
echo "== SupabaseClient<...> 사용 여부 (src/lib/supabase) =="
rg -n "SupabaseClient<" src/lib/supabase --type ts || true
echo "== createClient<...> 사용 여부 (src/lib/supabase) =="
rg -n "createClient<" src/lib/supabase --type ts || trueRepository: Siul49/moim
Length of output: 1540
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== src/lib/supabase/server.ts =="
sed -n '1,200p' src/lib/supabase/server.ts
echo "== src/lib/supabase/client.ts =="
sed -n '1,200p' src/lib/supabase/client.ts
echo "== SupabaseClient<...> 사용 여부 (src/lib/supabase) =="
rg -n "SupabaseClient<" src/lib/supabase --type ts || true
echo "== createClient<...> 사용 여부 (src/lib/supabase) =="
rg -n "createClient<" src/lib/supabase --type ts || trueRepository: Siul49/moim
Length of output: 1540
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo 전체에서 Database 타입 정의 찾기 =="
rg -n --hidden --no-ignore-vcs "export type Database|interface Database|type Database" . --type ts || true
echo "== repo 전체에서 Supabase types 경로(예: supabase/types) 흔적 찾기 =="
rg -n --hidden --no-ignore-vcs "supabase\\.gen\\.|supabase gen types typescript|gen types" package.json .github src . --type ts --type js --type json --type md || true
echo "== repo 전체에서 '`@/types/supabase`' 참조 찾기 =="
rg -n --hidden --no-ignore-vcs "`@/types/supabase`" . --type ts --type tsx || true
echo "== repo 전체에서 'Database<' 제네릭 사용 흔적 찾기 =="
rg -n --hidden --no-ignore-vcs "SupabaseClient<Database>|createClient<Database>" . --type ts --type tsx || true
echo "== 타입 파일 유무: src/types 디렉토리 파일 목록 =="
ls -la src/types || true
find src/types -maxdepth 2 -type f -print || trueRepository: Siul49/moim
Length of output: 967
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo 전체에서 Database 타입 정의 찾기 =="
rg -n --hidden --no-ignore-vcs "export type Database|interface Database|type Database" . --type ts || true
echo "== repo 전체에서 Supabase types 경로(예: supabase/types) 흔적 찾기 =="
rg -n --hidden --no-ignore-vcs "supabase\\.gen\\.|supabase gen types typescript|gen types" package.json .github src . --type ts --type js --type json --type md || true
echo "== repo 전체에서 '`@/types/supabase`' 참조 찾기 =="
rg -n --hidden --no-ignore-vcs "`@/types/supabase`" . --type ts --type tsx || true
echo "== repo 전체에서 'Database<' 제네릭 사용 흔적 찾기 =="
rg -n --hidden --no-ignore-vcs "SupabaseClient<Database>|createClient<Database>" . --type ts --type tsx || true
echo "== 타입 파일 유무: src/types 디렉토리 파일 목록 =="
ls -la src/types || true
find src/types -maxdepth 2 -type f -print || trueRepository: Siul49/moim
Length of output: 967
Supabase 관리자 클라이언트에도 Database 제네릭을 연결해 쿼리 타입 안전성을 확보하세요.
src/lib/supabase/admin.ts의 createAdminClient()는 SupabaseClient를 그대로 반환하고, createClient(...)에도 Database 타입을 지정하지 않아 from("profiles").select("email") 같은 쿼리가 컴파일 타임 타입 체크 없이 동작합니다. (컬럼 오타/스키마 변경이 런타임까지 늦게 노출)
현재 src/types/에 @/types/supabase(generated Database) 타입이 없어 그대로는 컴파일이 불가하므로, 먼저 Supabase 타입을 생성/추가한 뒤 제네릭을 적용해야 합니다.
♻️ 타입 안전성 추가
-import { createClient, type SupabaseClient } from "`@supabase/supabase-js`";
+import { createClient, type SupabaseClient } from "`@supabase/supabase-js`";
+import type { Database } from "`@/types/supabase`";
-let cached: SupabaseClient | null = null;
+let cached: SupabaseClient<Database> | null = null;
-export function createAdminClient(): SupabaseClient {
+export function createAdminClient(): SupabaseClient<Database> {
if (cached) return cached;
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!url || !serviceRoleKey) {
throw new Error(
"Supabase 관리자 클라이언트 설정이 누락되었습니다 (NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY).",
);
}
- cached = createClient(url, serviceRoleKey, {
+ cached = createClient<Database>(url, serviceRoleKey, {
auth: { autoRefreshToken: false, persistSession: false },
});
return cached;
}@/types/supabase는 현재 저장소에 존재하지 않으니 supabase gen types typescript로 생성(또는 동일한 Database 타입 파일을 추가)한 다음 위 적용을 진행하세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { createClient, type SupabaseClient } from "@supabase/supabase-js"; | |
| let cached: SupabaseClient | null = null; | |
| export function createAdminClient(): SupabaseClient { | |
| if (cached) return cached; | |
| const url = process.env.NEXT_PUBLIC_SUPABASE_URL; | |
| const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; | |
| if (!url || !serviceRoleKey) { | |
| throw new Error( | |
| "Supabase 관리자 클라이언트 설정이 누락되었습니다 (NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY).", | |
| ); | |
| } | |
| cached = createClient(url, serviceRoleKey, { | |
| auth: { autoRefreshToken: false, persistSession: false }, | |
| }); | |
| return cached; | |
| } | |
| import { createClient, type SupabaseClient } from "`@supabase/supabase-js`"; | |
| import type { Database } from "`@/types/supabase`"; | |
| let cached: SupabaseClient<Database> | null = null; | |
| export function createAdminClient(): SupabaseClient<Database> { | |
| if (cached) return cached; | |
| const url = process.env.NEXT_PUBLIC_SUPABASE_URL; | |
| const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; | |
| if (!url || !serviceRoleKey) { | |
| throw new Error( | |
| "Supabase 관리자 클라이언트 설정이 누락되었습니다 (NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY).", | |
| ); | |
| } | |
| cached = createClient<Database>(url, serviceRoleKey, { | |
| auth: { autoRefreshToken: false, persistSession: false }, | |
| }); | |
| return cached; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/supabase/admin.ts` around lines 12 - 33, The admin Supabase client
lacks the Database generic so query types aren't checked; generate the Supabase
types (e.g., run `supabase gen types typescript` to produce a Database type
file) and then update createAdminClient: import the generated Database type
(e.g., Database from '`@/types/supabase`'), change the cached variable and
function signature to use SupabaseClient<Database>, and call
createClient<Database>(...) so the returned client and all queries like
from("profiles").select(...) are type-safe.
There was a problem hiding this comment.
좋은 지적이라 반영하고 싶지만, 지금은 보류합니다. createClient<Database>에 넣을 생성된 Supabase 타입(@/types/supabase)이 아직 리포에 없습니다(실 Supabase 프로젝트 미연결 상태). 타입을 supabase gen types로 생성하는 작업과 함께 적용하는 게 맞아, 상위 이슈 #45의 후속(실 프로젝트 셋업)으로 넘깁니다. 그 전까지는 쿼리 타입 안전성이 없다는 점 인지하고 있습니다.
There was a problem hiding this comment.
@kokkumong 납득할 수 있는 이유입니다. 실 Supabase 프로젝트 연결 및 supabase gen types typescript 실행 후 한 번에 적용하는 흐름이 맞습니다.
이 항목을 놓치지 않으려면 후속 이슈를 열어 두는 걸 권장합니다. 원하시면 바로 생성해 드릴 수 있습니다.
(ᵔ ᴥ ᵔ)っ
- login: admin profiles 조회 error를 throw해 서버오류가 401로 위장되지 않게 함 - login: signInWithPassword 결과에서 data.session까지 검사 (이메일 확인 시 미인증 차단) - me: profiles 조회 error를 surface(500)해 폴백값으로 가리지 않게 함 - logout: signOut error 로깅 추가 - signup: 중복 판정 시 error.status를 우선 사용하고 메시지 정규식은 fallback으로 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2ef26d7 to
e3c3419
Compare
🚀 작업 내용 (What)
me·logout을 Supabase 세션 기반으로 교체 (getSession,signOut)signInWithPasswordprofiles확장 마이그레이션 추가 — 가입 부가정보(전화·약관 동의 등)를 트리거로 자동 반영lib/supabase/admin.ts(service role 클라이언트)/api/auth/kakao/*,lib/auth/{jwt,kakao,password}.ts,.env.example의 JWT/카카오 키📣 핵심 변경 이유 (Why)
📸 스크린샷 (Visuals, 선택)
🔗 관련 이슈 (Issue)
Close #45