전수 E2E 순회·소유자 결정 6건 구현·디자인 감사 반영
- 전수 순회: IA 전 라우트 412개 기능 인벤토리를 체크리스트 백로그로 관리 (docs/ops/e2e-full-sweep-2026-07-27.md), 신규 full-sweep 스펙 10파일 추가. RED→GREEN으로 결함 12건 수정: 관리자 무한 렌더 프리즈(TanStack autoReset 루프), 복수 코호트 저장 유실, 페르소나 보관 503(SQL 컬럼 모호성), PII 과잉 마스킹, /admin/ai watchdog 오탐 오버레이, 모바일 겹침 2건, 설정 스크롤 스파이, 온보딩 전화번호 무검증, 리뷰 조사·난도 라벨, pending 피드백 등. - 소유자 결정 구현: 설정 아바타 변경, 동의 철회·재동의 전체 흐름, 신규 학습자 기초 우선 추천, 학생 분석 테이블 가상화(@tanstack/react-virtual), 저작 모드 죽은 레일 정리, 감정 밸런스 타임라인 차트(deep turn_valence + 결정론 파생 폴백). - 디자인 감사(142차): 라이트 팔레트 AA 대비, 다크 토큰 별칭 통일, 미정의 CSS 변수 정리, 한글 keep-all 전역화, 탭 타깃 24px, LCP preconnect. - 검증: npm run e2e 병렬 432 수집 GREEN + 직렬 49/49 exit 0, 백엔드 pytest 421, gateway 29, typecheck/build/design-ssot/dead-code/중복 게이트 통과, layout-visual-gate 15/15, session-layout 8/8. 상세는 SSOT 대시보드 142~144차 노트.
This commit is contained in:
parent
0ae94499f2
commit
4511383cd9
55 changed files with 9333 additions and 573 deletions
|
|
@ -7,8 +7,10 @@ import time
|
|||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
import re
|
||||
|
||||
from fastapi import APIRouter, File, HTTPException, Query, UploadFile, status
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
|
||||
from ..auth_types import RoleName
|
||||
from ..auth_sessions import (
|
||||
|
|
@ -128,6 +130,20 @@ class UserProfileResponse(BaseModel):
|
|||
onboarding_required: bool = True
|
||||
|
||||
|
||||
# 연락처: 선행 + 허용, 숫자·하이픈·공백·괄호·점만, 숫자 9~15자리.
|
||||
_PHONE_ALLOWED_RE = re.compile(r"^\+?[0-9()\-\s.]+$")
|
||||
|
||||
|
||||
def _validate_phone_format(value: str) -> str:
|
||||
trimmed = value.strip()
|
||||
digits = re.sub(r"\D", "", trimmed)
|
||||
if not _PHONE_ALLOWED_RE.fullmatch(trimmed) or not 9 <= len(digits) <= 15:
|
||||
raise ValueError(
|
||||
"연락처는 숫자 9~15자리의 전화번호 형식이어야 합니다 (예: 010-1234-5678)."
|
||||
)
|
||||
return trimmed
|
||||
|
||||
|
||||
class UserProfilePatch(BaseModel):
|
||||
display_name: str | None = Field(default=None, min_length=1, max_length=80)
|
||||
affiliation: str | None = Field(default=None, max_length=120)
|
||||
|
|
@ -140,6 +156,13 @@ class UserProfilePatch(BaseModel):
|
|||
self_introduction: str | None = Field(default=None, max_length=600)
|
||||
avatar_url: str | None = Field(default=None, max_length=500)
|
||||
|
||||
@field_validator("phone")
|
||||
@classmethod
|
||||
def _check_phone(cls, value: str | None) -> str | None:
|
||||
if value is None:
|
||||
return value
|
||||
return _validate_phone_format(value)
|
||||
|
||||
|
||||
class OnboardingRequest(BaseModel):
|
||||
legal_name: str = Field(..., min_length=1, max_length=80)
|
||||
|
|
@ -154,6 +177,11 @@ class OnboardingRequest(BaseModel):
|
|||
terms_accepted: bool
|
||||
privacy_accepted: bool
|
||||
|
||||
@field_validator("phone")
|
||||
@classmethod
|
||||
def _check_phone(cls, value: str) -> str:
|
||||
return _validate_phone_format(value)
|
||||
|
||||
|
||||
class AvatarUploadResponse(BaseModel):
|
||||
avatar_url: str
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue