123 lines
3.9 KiB
Bash
123 lines
3.9 KiB
Bash
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
# 1) Репозиторий: приводить user_id к uuid.UUID
|
||
cat > services/profiles/src/app/repositories/profile_repository.py <<'PY'
|
||
import uuid
|
||
from typing import Optional
|
||
|
||
from sqlalchemy import select
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.models.profile import Profile
|
||
from app.schemas.profile import ProfileCreate
|
||
|
||
class ProfileRepository:
|
||
def __init__(self, db: Session):
|
||
self.db = db
|
||
|
||
@staticmethod
|
||
def _to_uuid(v) -> uuid.UUID:
|
||
if isinstance(v, uuid.UUID):
|
||
return v
|
||
return uuid.UUID(str(v))
|
||
|
||
def get_by_user(self, user_id) -> Optional[Profile]:
|
||
uid = self._to_uuid(user_id)
|
||
stmt = select(Profile).where(Profile.user_id == uid)
|
||
return self.db.execute(stmt).scalar_one_or_none()
|
||
|
||
def create(self, user_id, obj: ProfileCreate) -> Profile:
|
||
uid = self._to_uuid(user_id)
|
||
p = Profile(
|
||
user_id=uid,
|
||
gender=obj.gender,
|
||
city=obj.city,
|
||
languages=obj.languages or [],
|
||
interests=obj.interests or [],
|
||
)
|
||
self.db.add(p)
|
||
self.db.commit()
|
||
self.db.refresh(p)
|
||
return p
|
||
PY
|
||
|
||
# 2) Схемы: дефолты - пустые списки (чтобы не было None → JSONB)
|
||
cat > services/profiles/src/app/schemas/profile.py <<'PY'
|
||
from __future__ import annotations
|
||
from typing import Optional, List
|
||
from pydantic import BaseModel, Field
|
||
|
||
class ProfileBase(BaseModel):
|
||
gender: str
|
||
city: str
|
||
languages: List[str] = Field(default_factory=list)
|
||
interests: List[str] = Field(default_factory=list)
|
||
|
||
class ProfileCreate(ProfileBase):
|
||
pass
|
||
|
||
class ProfileOut(ProfileBase):
|
||
id: str
|
||
user_id: str
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
PY
|
||
|
||
# 3) Роут: ловим ошибки явно → 400 вместо 500
|
||
cat > services/profiles/src/app/api/routes/profiles.py <<'PY'
|
||
from fastapi import APIRouter, Depends, HTTPException, status
|
||
from sqlalchemy.orm import Session
|
||
from sqlalchemy.exc import IntegrityError, DataError
|
||
|
||
from app.db.deps import get_db
|
||
from app.schemas.profile import ProfileCreate, ProfileOut
|
||
from app.services.profile_service import ProfileService
|
||
from app.core.security import get_current_user # возвращает объект с полями sub, email, role
|
||
|
||
router = APIRouter(prefix="/v1/profiles", tags=["profiles"])
|
||
|
||
@router.get("/me", response_model=ProfileOut)
|
||
def get_my_profile(db: Session = Depends(get_db), user=Depends(get_current_user)):
|
||
svc = ProfileService(db)
|
||
p = svc.get_by_user(user.sub)
|
||
if not p:
|
||
# 404, если профиль отсутствует
|
||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found")
|
||
return p
|
||
|
||
@router.post("", response_model=ProfileOut, status_code=status.HTTP_201_CREATED)
|
||
def create_profile(payload: ProfileCreate, db: Session = Depends(get_db), user=Depends(get_current_user)):
|
||
svc = ProfileService(db)
|
||
try:
|
||
existing = svc.get_by_user(user.sub)
|
||
if existing:
|
||
return existing
|
||
p = svc.create(user.sub, payload)
|
||
return p
|
||
except (IntegrityError, DataError, ValueError) as exc:
|
||
db.rollback()
|
||
raise HTTPException(status_code=400, detail=f"Invalid data: {exc}")
|
||
PY
|
||
|
||
# 4) Сервис — тонкая обёртка над репозиторием
|
||
cat > services/profiles/src/app/services/profile_service.py <<'PY'
|
||
from sqlalchemy.orm import Session
|
||
from app.repositories.profile_repository import ProfileRepository
|
||
from app.schemas.profile import ProfileCreate
|
||
|
||
class ProfileService:
|
||
def __init__(self, db: Session):
|
||
self.repo = ProfileRepository(db)
|
||
|
||
def get_by_user(self, user_id):
|
||
return self.repo.get_by_user(user_id)
|
||
|
||
def create(self, user_id, obj: ProfileCreate):
|
||
return self.repo.create(user_id, obj)
|
||
PY
|
||
|
||
echo "[profiles] rebuilding..."
|
||
docker compose build profiles
|
||
docker compose restart profiles |