api endpoints fix and inclusion
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-08-10 15:39:48 +09:00
parent 7ecc556c77
commit b595bcc9bc
65 changed files with 6046 additions and 263 deletions

View File

@@ -1,134 +1,310 @@
#!/usr/bin/env bash
set -euo pipefail
CONF="infra/gateway/nginx.conf"
[[ -f "$CONF" ]] || { echo "[ERR] $CONF not found"; exit 1; }
cp "$CONF" "$CONF.bak.$(date +%s)"
echo "[OK] backup saved"
cat > "$CONF" <<'NGINX'
server {
listen 80;
server_name _;
# Docker DNS
resolver 127.0.0.11 ipv6=off valid=10s;
# Health of gateway itself
location = /health {
default_type application/json;
return 200 '{"status":"ok","gateway":"nginx"}';
}
# ===== Unified API Docs (docs aggregator) =====
location = /docs {
proxy_pass http://marriage_docs:8000/docs;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /redoc {
proxy_pass http://marriage_docs:8000/redoc;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /openapi.json {
proxy_pass http://marriage_docs:8000/openapi.json;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /refresh {
proxy_pass http://marriage_docs:8000/refresh;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /_health {
proxy_pass http://marriage_docs:8000/_health;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# ===== Microservices (prefix strip) =====
location /auth/ {
rewrite ^/auth/(.*)$ /$1 break;
proxy_pass http://marriage_auth:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Authorization $http_authorization;
}
location /profiles/ {
rewrite ^/profiles/(.*)$ /$1 break;
proxy_pass http://marriage_profiles:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /match/ {
rewrite ^/match/(.*)$ /$1 break;
proxy_pass http://marriage_match:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /chat/ {
rewrite ^/chat/(.*)$ /$1 break;
proxy_pass http://marriage_chat:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /payments/ {
rewrite ^/payments/(.*)$ /$1 break;
proxy_pass http://marriage_payments:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
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"
}
NGINX
echo "[OK] nginx.conf updated"
# ---------- 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
echo "[INFO] restarting gateway..."
docker compose restart gateway >/dev/null
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
echo "[INFO] quick checks:"
echo -n "gateway: "; curl -s http://localhost:8080/health; echo
echo -n "docs/_health:"; curl -s http://localhost:8080/_health; echo
for svc in auth profiles match chat payments; do
code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8080/$svc/health")
echo "$svc/health: $code"
done
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"