feat(auth): 카카오 로그인 시 약관 동의 레코드 저장 구현 - #72
Conversation
- 로그인 시 user_terms_agreements 테이블에 service, privacy 약관 동의 레코드 자동 생성 - saveFirstLoginFields에서 $transaction으로 유저 업데이트와 약관 동의 레코드 생성을 원자적 처리 - 약관 버전 상수 정의 (날짜 기반: 2026-06-18) - 기존 유저 대상 backfill 스크립트 추가 (scripts/backfill-user-terms-agreements.ts) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
Changes약관 동의 트랜잭션 확장 및 사용자 프로필 타입 정의
예상 코드 리뷰 노력🎯 2 (Simple) | ⏱️ ~10 minutes 관련 가능성 있는 PR
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
- API 라우트 디렉토리 이동 (src/app/api/user/me → src/app/api/users/me) - domains/user/hooks.ts 내 API 호출 경로 일괄 수정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@scripts/backfill-user-terms-agreements.ts`:
- Around line 13-45: The backfill script is hardcoding the INITIAL_TERMS_VERSION
constant and TermsType values in the main() function instead of importing them
from the centralized src/domains/auth/constants.ts file. Remove the hardcoded
INITIAL_TERMS_VERSION constant declaration at the top of the file and add
imports for both INITIAL_TERMS_VERSION and TermsType from
src/domains/auth/constants.ts to ensure consistency between the backfill script
and the authentication login path version management.
- Around line 16-20: In the usersWithoutAgreements query filter, change the
condition from `termsAgreements: { none: {} }` to `termsAgreements: { some: {}
}` to include users with existing (even partially corrupted) agreement records
in the backfill scope. Additionally, add a unique constraint to the
UserTermsAgreement schema model definition such as `@@unique([userId,
termsType])` to establish the unique constraint that the `skipDuplicates: true`
parameter in the createMany operation depends on to prevent duplicate
insertions.
In `@src/lib/auth/index.ts`:
- Around line 46-54: The userTermsAgreement.createMany operation in the
saveFirstLoginFields function lacks idempotency protection, allowing duplicate
records when the function is called multiple times (from both signIn and
createUser code paths). Fix this by adding a composite unique constraint on the
UserTermsAgreement model for the fields userId, termsType, and termsVersion in
the Prisma schema, and then add the skipDuplicates: true option to all
createMany calls for userTermsAgreement (there are multiple calls across the
saveFirstLoginFields function at different sections) to prevent duplicate
inserts while maintaining transaction atomicity.
🪄 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: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 5b8b412d-4cdf-4ce0-b42f-9bb384c862de
📒 Files selected for processing (3)
scripts/backfill-user-terms-agreements.tssrc/domains/auth/constants.tssrc/lib/auth/index.ts
| const INITIAL_TERMS_VERSION = '2026-06-18' | ||
|
|
||
| async function main() { | ||
| const usersWithoutAgreements = await prisma.user.findMany({ | ||
| where: { | ||
| termsAgreedAt: { not: null }, | ||
| termsAgreements: { none: {} }, | ||
| }, | ||
| select: { id: true, termsAgreedAt: true }, | ||
| }) | ||
|
|
||
| console.log(`backfill 대상 유저 수: ${usersWithoutAgreements.length}`) | ||
|
|
||
| if (usersWithoutAgreements.length === 0) { | ||
| console.log('backfill 대상 없음. 종료.') | ||
| return | ||
| } | ||
|
|
||
| const records = usersWithoutAgreements.flatMap((user) => [ | ||
| { | ||
| userId: user.id, | ||
| termsType: TermsType.SERVICE, | ||
| termsVersion: INITIAL_TERMS_VERSION, | ||
| isRequired: true, | ||
| agreedAt: user.termsAgreedAt!, | ||
| }, | ||
| { | ||
| userId: user.id, | ||
| termsType: TermsType.PRIVACY, | ||
| termsVersion: INITIAL_TERMS_VERSION, | ||
| isRequired: true, | ||
| agreedAt: user.termsAgreedAt!, | ||
| }, |
There was a problem hiding this comment.
백필 스크립트가 중앙 약관 버전 상수를 우회하고 있습니다.
src/domains/auth/constants.ts에서 버전을 중앙관리하도록 바뀌었는데, 여기서는 INITIAL_TERMS_VERSION/TermsType를 하드코딩하고 있어 로그인 경로와 백필 경로의 버전 계약이 쉽게 분기됩니다.
제안 변경안
-import { PrismaClient, TermsType } from '`@prisma/client`'
+import { PrismaClient } from '`@prisma/client`'
+import { CURRENT_TERMS_VERSIONS } from '../src/domains/auth/constants'
const prisma = new PrismaClient()
-const INITIAL_TERMS_VERSION = '2026-06-18'
-
async function main() {
@@
- const records = usersWithoutAgreements.flatMap((user) => [
- {
- userId: user.id,
- termsType: TermsType.SERVICE,
- termsVersion: INITIAL_TERMS_VERSION,
- isRequired: true,
- agreedAt: user.termsAgreedAt!,
- },
- {
- userId: user.id,
- termsType: TermsType.PRIVACY,
- termsVersion: INITIAL_TERMS_VERSION,
- isRequired: true,
- agreedAt: user.termsAgreedAt!,
- },
- ])
+ const records = usersWithoutAgreements.flatMap((user) =>
+ Object.values(CURRENT_TERMS_VERSIONS).map((terms) => ({
+ userId: user.id,
+ termsType: terms.type,
+ termsVersion: terms.version,
+ isRequired: terms.isRequired,
+ agreedAt: user.termsAgreedAt!,
+ })),
+ )🤖 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 `@scripts/backfill-user-terms-agreements.ts` around lines 13 - 45, The backfill
script is hardcoding the INITIAL_TERMS_VERSION constant and TermsType values in
the main() function instead of importing them from the centralized
src/domains/auth/constants.ts file. Remove the hardcoded INITIAL_TERMS_VERSION
constant declaration at the top of the file and add imports for both
INITIAL_TERMS_VERSION and TermsType from src/domains/auth/constants.ts to ensure
consistency between the backfill script and the authentication login path
version management.
| const usersWithoutAgreements = await prisma.user.findMany({ | ||
| where: { | ||
| termsAgreedAt: { not: null }, | ||
| termsAgreements: { none: {} }, | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) 백필 필터가 '레코드 0개'만 대상으로 하는지 확인"
rg -n -C3 'termsAgreements:\s*\{\s*none:\s*\{\s*\}\s*\}' scripts/backfill-user-terms-agreements.ts
echo
echo "2) UserTermsAgreement 모델의 유니크 제약 확인"
rg -n -A30 -B2 'model UserTermsAgreement' prisma/schema.prisma
rg -n '@@unique|`@unique`' prisma/schema.prismaRepository: I5-Project/TALKY-OWL
Length of output: 3124
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Lines 16-50+ of backfill script to see full context ==="
cat -n scripts/backfill-user-terms-agreements.ts | head -60
echo
echo "=== Check for skipDuplicates and createMany usage ==="
rg -n -A5 -B5 'skipDuplicates|createMany|createManyAndReturn' scripts/backfill-user-terms-agreements.tsRepository: I5-Project/TALKY-OWL
Length of output: 2345
쿼리 필터가 부분 손상 유저를 영구 제외하고, skipDuplicates가 실제로는 작동하지 않습니다.
termsAgreements: { none: {} } (line 19)는 약관 레코드가 0개인 유저만 선택합니다. 이미 1개 이상의 부분 손상 상태인 유저는 영구적으로 백필 대상에서 제외되어 데이터 불일치가 발생합니다.
또한 createMany(..., skipDuplicates: true) (line 50)는 UserTermsAgreement 모델에 유니크 제약이 없으므로 실제로 중복을 방지하지 못합니다. skipDuplicates는 유니크 제약에 의존하는데, 현재 스키마에는 그런 제약이 없습니다(userId + termsType 복합 유니크 등이 필요).
해결책:
- 필터를
termsAgreements: { some: {} }또는 별도 로직으로 변경하여 부분 손상 유저도 포함 UserTermsAgreement스키마에 적절한 유니크 제약 추가 (예:@@unique([userId, termsType]))
🤖 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 `@scripts/backfill-user-terms-agreements.ts` around lines 16 - 20, In the
usersWithoutAgreements query filter, change the condition from `termsAgreements:
{ none: {} }` to `termsAgreements: { some: {} }` to include users with existing
(even partially corrupted) agreement records in the backfill scope.
Additionally, add a unique constraint to the UserTermsAgreement schema model
definition such as `@@unique([userId, termsType])` to establish the unique
constraint that the `skipDuplicates: true` parameter in the createMany operation
depends on to prevent duplicate insertions.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/users/me/route.ts`:
- Around line 15-17: Move the session retrieval call to
getServerSession(authOptions) and the subsequent getSessionUserId(session)
validation inside the try block of both the GET and PATCH functions in the route
handler. Currently these calls are outside the try/catch block, which means any
exceptions thrown by getServerSession will not be caught and will result in a
default 500 response instead of the properly formatted ApiResponse structure.
Ensure that both functions handle session initialization and userId extraction
within their respective try blocks to comply with the API response format
guidelines.
- Around line 139-146: The catch block handling the prisma.user.update call is
missing a check for error code P2025, which is thrown when the user record no
longer exists in the database. Add an additional condition after the existing
P2002 check to detect when err.code equals P2025, and return a 404
NextResponse.json with an ApiResponse containing success: false and error code
USER_NOT_FOUND with an appropriate message, matching the pattern used in the GET
endpoint for consistency.
- Around line 78-107: The code uses TypeScript's `as PatchBody` type assertion
which provides no runtime validation, allowing non-string values to be passed
and causing errors when calling `.trim()` on `body.name` and `body.nickname`, or
`.toUpperCase()` on `body.mbti`. Before calling any string methods on these
fields, add `typeof` checks to verify that `body.name`, `body.nickname`, and
`body.mbti` are actually strings, and handle the cases where they are not (such
as when they are null, undefined, or numbers) by adding appropriate error
entries to the `fieldErrors` array with appropriate error codes and messages.
🪄 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: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 6d637146-6249-48dd-a022-f8ce3ee0fb95
📒 Files selected for processing (3)
src/app/api/user/me/route.tssrc/app/api/users/me/route.tssrc/domains/user/hooks.ts
💤 Files with no reviewable changes (1)
- src/app/api/user/me/route.ts
| export async function GET() { | ||
| const session = await getServerSession(authOptions) | ||
| const userId = getSessionUserId(session) |
There was a problem hiding this comment.
세션 조회도 try/catch 안에서 처리하세요.
getServerSession(authOptions)가 예외를 던지면 현재 catch를 거치지 않아 ApiResponse 형식 대신 기본 500 응답이 나갈 수 있습니다. GET/PATCH 모두 세션 조회와 userId 검증을 핸들러 try 블록 안으로 옮겨 주세요.
As per coding guidelines, API responses must follow common response structure format; Try-catch error handling must be implemented in API Route Handlers and Server Services.
Also applies to: 66-68
🤖 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/users/me/route.ts` around lines 15 - 17, Move the session
retrieval call to getServerSession(authOptions) and the subsequent
getSessionUserId(session) validation inside the try block of both the GET and
PATCH functions in the route handler. Currently these calls are outside the
try/catch block, which means any exceptions thrown by getServerSession will not
be caught and will result in a default 500 response instead of the properly
formatted ApiResponse structure. Ensure that both functions handle session
initialization and userId extraction within their respective try blocks to
comply with the API response format guidelines.
Source: Coding guidelines
| } | ||
| } | ||
|
|
||
| const VALID_MBTI = [ |
There was a problem hiding this comment.
user type 파일 하나 만들까요? 저도 같은 거 써서 그냥 같이 쓸까봐요
| if (body.nickname !== undefined) { | ||
| const trimmed = body.nickname.trim() | ||
| if (!trimmed || trimmed.length < 2 || trimmed.length > 20) { | ||
| fieldErrors.push({ field: 'nickname', code: 'INVALID_NICKNAME', message: '닉네임은 2~20자로 입력해주세요.' }) |
|
|
||
| if (fieldErrors.length > 0) { | ||
| return NextResponse.json<ApiResponse>( | ||
| { success: false, error: { code: 'VALIDATION_ERROR', message: '입력값을 확인해주세요.', fieldErrors } }, |
There was a problem hiding this comment.
음 지시멘트가 너무 애매해서.. 한글 ㅇㅇ자 문장부호 안된다 이런식으로 작성해야될 것 같아요
Move UserMeDto, PatchUserBody, and VALID_MBTI from route handler into a shared type file for reuse across API and client code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- src/app/api/user/me/route.ts: dev 버전 유지 (프로필 이미지 업로드 포함) - src/app/api/users/me/route.ts: dev 버전 유지 - src/domains/user/hooks.ts: dev 버전 유지 (api 모듈 import 구조) - src/lib/auth/index.ts: 양쪽 변경사항 병합 (user.name 체크 + skipDuplicates) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Deployment failed with the following error: Learn More: https://vercel.com/lsgs-projects-34d31fd6?upgradeToPro=build-rate-limit |
Summary
user_terms_agreements테이블에 약관 동의 레코드(service, privacy) 자동 생성saveFirstLoginFields에서$transaction으로 유저 업데이트와 약관 동의 레코드 생성을 원자적 처리2026-06-18)scripts/backfill-user-terms-agreements.ts)src/types/user.ts공유 타입 파일 추출 (UserMeDto,PatchUserBody,VALID_MBTI)src/app/api/users/me/route.ts에서 인라인 타입/상수 제거, 공유 타입 import로 전환변경 파일
src/domains/auth/constants.ts— 약관 버전 상수 (SERVICE, PRIVACY /2026-06-18)src/lib/auth/index.ts— 로그인 시 약관 동의 레코드 생성 로직 추가 (트랜잭션)scripts/backfill-user-terms-agreements.ts— 기존 유저 backfill 스크립트src/types/user.ts— 공유 유저 타입 (UserMeDto,PatchUserBody,VALID_MBTI)src/app/api/users/me/route.ts— 인라인 타입 제거, 공유 타입 import 적용Test plan
user_terms_agreements테이블에 2건(service, privacy) 생성 확인user_terms_agreements레코드 없음 확인npx tsx scripts/backfill-user-terms-agreements.ts실행 후 기존 유저 backfill 정상 동작 확인src/types/user.ts타입이 API route에서 정상 import 확인🤖 Generated with Claude Code
Summary by CodeRabbit
릴리스 노트