diff --git a/backend/app/models.py b/backend/app/models.py index 1948adc..41b860b 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -128,6 +128,9 @@ class UserPrediction(Base): __tablename__ = "user_predictions" __table_args__ = ( UniqueConstraint("match_id", "device_id", name="uq_match_device"), + # 같은 이메일은 같은 경기에 1픽만 (NULL 이메일은 다수 허용 — NULL 은 서로 구별). + # 신규 DB 에만 자동 적용. 기존 테이블은 앱 로직(이메일 우선 식별)으로 보장. + UniqueConstraint("match_id", "email", name="uq_match_email"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) diff --git a/backend/app/routers/predictions.py b/backend/app/routers/predictions.py index 2beabaf..155b754 100644 --- a/backend/app/routers/predictions.py +++ b/backend/app/routers/predictions.py @@ -2,7 +2,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy import select, update +from sqlalchemy import func, select, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -63,15 +63,28 @@ async def submit_prediction( # outcome 은 스코어에서 도출 (입력과 불일치해도 스코어 기준으로 정규화) outcome = outcome_of(body.scoreA, body.scoreB) + email = str(body.email).strip().lower() if body.email else None - existing = ( - await db.execute( - select(UserPrediction).where( - UserPrediction.match_id == body.matchId, - UserPrediction.device_id == body.deviceId, + # 중복 식별: ① 이메일(있으면 1차 신원 — 다른 기기여도 동일인) ② 기기(deviceId) + existing = None + if email: + existing = ( + await db.execute( + select(UserPrediction).where( + UserPrediction.match_id == body.matchId, + func.lower(UserPrediction.email) == email, + ) ) - ) - ).scalars().first() + ).scalars().first() + if existing is None: + existing = ( + await db.execute( + select(UserPrediction).where( + UserPrediction.match_id == body.matchId, + UserPrediction.device_id == body.deviceId, + ) + ) + ).scalars().first() if existing: # 마감 전 1회 수정: 분포 보정 (이전 outcome 제거, 새 outcome 추가) @@ -79,8 +92,9 @@ async def submit_prediction( existing.outcome = outcome existing.score_a = body.scoreA existing.score_b = body.scoreB - if body.email: - existing.email = str(body.email) + existing.device_id = body.deviceId # 최신 제출 기기로 갱신 + if email: + existing.email = email existing.notify = body.notify pred = existing else: @@ -91,7 +105,7 @@ async def submit_prediction( outcome=outcome, score_a=body.scoreA, score_b=body.scoreB, - email=str(body.email) if body.email else None, + email=email, notify=body.notify, ) db.add(pred)