Files
marriage/scripts/patch.sh
Andrey K. Choi b595bcc9bc
Some checks failed
continuous-integration/drone/push Build is failing
api endpoints fix and inclusion
2025-08-10 15:39:48 +09:00

311 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
write() {
local path="$1"; shift
mkdir -p "$(dirname "$path")"
if [[ -f "$path" ]]; then cp -f "$path" "${path}.bak"; fi
cat >"$path" <<'PYEOF'
'"$@"'
PYEOF
echo "✔ wrote $path"
}
# ---------- chat/src/app/api/chat.py ----------
write chat/src/app/api/chat.py '
from __future__ import annotations
from typing import List
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.core.security import get_current_user, UserClaims
from app.schemas.chat import RoomCreate, RoomRead, MessageCreate, MessageRead
from app.services.chat_service import ChatService
router = APIRouter(prefix="/v1", tags=["chat"])
@router.post("/rooms", response_model=RoomRead, status_code=201)
def create_room(payload: RoomCreate, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
svc = ChatService(db)
room = svc.create_room(title=payload.title, participant_ids=payload.participants, creator_id=user.sub)
return room
@router.get("/rooms", response_model=list[RoomRead])
def my_rooms(db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
return ChatService(db).list_rooms_for_user(user.sub)
@router.get("/rooms/{room_id}", response_model=RoomRead)
def get_room(room_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
room = ChatService(db).get_room(room_id, user.sub)
if not room:
raise HTTPException(status_code=404, detail="Room not found")
return room
@router.post("/rooms/{room_id}/messages", response_model=MessageRead, status_code=201)
def send_message(room_id: UUID, payload: MessageCreate, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
svc = ChatService(db)
room = svc.get_room(room_id, user.sub)
if not room:
raise HTTPException(status_code=404, detail="Room not found")
msg = svc.create_message(room_id, user.sub, payload.content)
return msg
@router.get("/rooms/{room_id}/messages", response_model=list[MessageRead])
def list_messages(
room_id: UUID,
offset: int = 0,
limit: int = Query(100, le=500),
db: Session = Depends(get_db),
user: UserClaims = Depends(get_current_user),
):
svc = ChatService(db)
room = svc.get_room(room_id, user.sub)
if not room:
raise HTTPException(status_code=404, detail="Room not found")
return svc.list_messages(room_id, user.sub, offset, limit)
'
# ---------- match/src/app/api/routes/pairs.py ----------
write match/src/app/api/routes/pairs.py '
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.core.security import get_current_user, require_roles, UserClaims
from app.schemas.pair import PairCreate, PairUpdate, PairRead
from app.services.pair_service import PairService
router = APIRouter(prefix="/v1/pairs", tags=["pairs"])
@router.post("", response_model=PairRead, status_code=201)
def create_pair(
payload: PairCreate,
db: Session = Depends(get_db),
user: UserClaims = Depends(require_roles("ADMIN","MATCHMAKER")),
):
svc = PairService(db)
return svc.create(
user_id_a=payload.user_id_a,
user_id_b=payload.user_id_b,
score=payload.score,
notes=payload.notes,
created_by=user.sub,
)
@router.get("", response_model=list[PairRead])
def list_pairs(
for_user_id: str | None = None,
status: str | None = None,
offset: int = 0,
limit: int = Query(50, le=200),
db: Session = Depends(get_db),
_: UserClaims = Depends(get_current_user),
):
return PairService(db).list(for_user_id=for_user_id, status=status, offset=offset, limit=limit)
@router.get("/{pair_id}", response_model=PairRead)
def get_pair(pair_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
pair = PairService(db).get(pair_id)
if not pair:
raise HTTPException(status_code=404, detail="Pair not found")
return pair
@router.patch("/{pair_id}", response_model=PairRead)
def update_pair(
pair_id: UUID,
payload: PairUpdate,
db: Session = Depends(get_db),
user: UserClaims = Depends(require_roles("ADMIN")),
):
updated = PairService(db).update(pair_id, payload)
if not updated:
raise HTTPException(status_code=404, detail="Pair not found")
return updated
@router.post("/{pair_id}/accept", response_model=PairRead)
def accept(pair_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
res = PairService(db).accept(pair_id, user.sub)
if not res:
raise HTTPException(status_code=404, detail="Pair not found")
return res
@router.post("/{pair_id}/reject", response_model=PairRead)
def reject(pair_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
res = PairService(db).reject(pair_id, user.sub)
if not res:
raise HTTPException(status_code=404, detail="Pair not found")
return res
@router.delete("/{pair_id}", status_code=204)
def delete_pair(
pair_id: UUID,
db: Session = Depends(get_db),
_: UserClaims = Depends(require_roles("ADMIN","MATCHMAKER")),
):
svc = PairService(db)
obj = svc.get(pair_id)
if not obj:
return
svc.delete(obj)
'
# ---------- profiles/src/app/api/routes/profiles.py ----------
write profiles/src/app/api/routes/profiles.py '
from __future__ import annotations
from typing import List, Optional
from uuid import UUID
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, Query, status
from sqlalchemy.orm import Session
import os
from ...core.security import get_current_user, require_roles, UserClaims
from ...db.session import get_db
from ...models.profile import Profile
from ...schemas.profile import ProfileCreate, ProfileUpdate, ProfileOut, LikesList
from ...services.profile_service import ProfileService
from ...services.profile_search_service import ProfileSearchService
from ...services.likes_service import LikesService
router = APIRouter(prefix="/v1/profiles", tags=["profiles"])
UPLOAD_DIR = os.getenv("UPLOAD_DIR", "/app/uploads")
BASE_EXTERNAL_URL = os.getenv("BASE_EXTERNAL_URL", "http://localhost:8080")
@router.get("/me", response_model=ProfileOut)
def get_me(db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
prof = ProfileService(db).get_by_user_id(user.sub)
if not prof:
raise HTTPException(status_code=404, detail="Profile not found")
return prof
@router.post("", response_model=ProfileOut, status_code=201)
def create_profile(payload: ProfileCreate, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
svc = ProfileService(db)
if svc.get_by_user_id(user.sub):
raise HTTPException(status_code=409, detail="Profile already exists")
return svc.create(user.sub, **payload.model_dump())
@router.get("", response_model=List[ProfileOut])
def list_profiles(
q: Optional[str] = None,
gender: Optional[str] = Query(None, pattern="^(male|female|other)$"),
city: Optional[str] = None,
languages: Optional[List[str]] = Query(None),
interests: Optional[List[str]] = Query(None),
has_photo: Optional[bool] = None,
sort_by: Optional[str] = Query(None, pattern="^(created_at|updated_at|city|gender)$"),
order: Optional[str] = Query("asc", pattern="^(asc|desc)$"),
offset: int = 0,
limit: int = Query(50, le=200),
db: Session = Depends(get_db),
user: UserClaims = Depends(get_current_user),
):
svc = ProfileSearchService(db)
return svc.list_profiles(
q=q, gender=gender, city=city,
languages=languages, interests=interests, has_photo=has_photo,
sort_by=sort_by, order=order, offset=offset, limit=limit,
)
@router.get("/{profile_id}", response_model=ProfileOut)
def get_profile(profile_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
prof = ProfileService(db).get(profile_id)
if not prof:
raise HTTPException(status_code=404, detail="Profile not found")
return prof
@router.get("/by-user/{user_id}", response_model=ProfileOut)
def get_by_user(user_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
prof = ProfileService(db).get_by_user_id(user_id)
if not prof:
raise HTTPException(status_code=404, detail="Profile not found")
return prof
@router.patch("/me", response_model=ProfileOut)
def patch_me(payload: ProfileUpdate, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
svc = ProfileService(db)
prof = svc.get_by_user_id(user.sub)
if not prof:
raise HTTPException(status_code=404, detail="Not found")
data = payload.model_dump(exclude_none=True)
return svc.update(prof, **data)
@router.delete("/me", status_code=204)
def delete_me(db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
svc = ProfileService(db)
prof = svc.get_by_user_id(user.sub)
if not prof:
return
svc.delete(prof)
return
@router.post("/me/photo", response_model=ProfileOut)
def upload_photo(
file: UploadFile = File(...),
db: Session = Depends(get_db),
user: UserClaims = Depends(get_current_user),
):
svc = ProfileService(db)
prof = svc.get_by_user_id(user.sub)
if not prof:
raise HTTPException(status_code=404, detail="Not found")
if file.content_type not in ("image/jpeg", "image/png", "image/webp"):
raise HTTPException(status_code=400, detail="Invalid content-type")
os.makedirs(UPLOAD_DIR, exist_ok=True)
ext = {"image/jpeg": "jpg", "image/png": "png", "image/webp": "webp"}[file.content_type]
subdir = os.path.join(UPLOAD_DIR, "avatars")
os.makedirs(subdir, exist_ok=True)
filename = f"{user.sub}.{ext}"
path = os.path.join(subdir, filename)
with open(path, "wb") as f:
f.write(file.file.read())
public_url = f"{BASE_EXTERNAL_URL}/profiles/static/avatars/{filename}"
return svc.update(prof, photo_url=public_url)
@router.delete("/me/photo", response_model=ProfileOut)
def delete_photo(db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
svc = ProfileService(db)
prof = svc.get_by_user_id(user.sub)
if not prof:
raise HTTPException(status_code=404, detail="Not found")
return svc.update(prof, photo_url=None)
# Back-compat stub (hidden from schema)
@router.get("/../likes", response_model=LikesList, include_in_schema=False)
def _compat_likes_redirect():
return LikesList(items=[])
likes_router = APIRouter(prefix="/v1/likes", tags=["profiles"])
@likes_router.get("", response_model=LikesList)
def my_likes(db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
items = LikesService(db).list_my_likes(user.sub)
return LikesList(items=items)
@likes_router.put("/{target_user_id}", status_code=status.HTTP_204_NO_CONTENT)
def put_like(target_user_id: str, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
LikesService(db).put_like(user.sub, target_user_id)
return
@likes_router.delete("/{target_user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_like(target_user_id: str, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
LikesService(db).delete_like(user.sub, target_user_id)
return
@likes_router.get("/mutual", response_model=List[str])
def mutual(db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
return LikesService(db).mutual(user.sub)
'
echo
echo "Done. Backups saved as *.bak where files existed."
echo "Rebuild & restart affected services, then re-run your audit:"
echo " docker compose build chat match profiles && docker compose up -d"
echo " ./scripts/audit.sh"