feat(predictions): 이메일도 중복 투표 방지 (이메일 우선 식별)
같은 이메일은 같은 경기에 1픽만 — 다른 기기에서 같은 이메일로 제출 시 새 픽 추가 대신 기존 픽 수정 + crowd 보정. 이메일은 소문자 정규화 저장. 모델에 (match_id, email) 유니크 제약 추가(신규 DB 적용, 기존 테이블은 앱 로직 보장). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>develop
parent
86290a7c69
commit
c9244bc42d
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue