api endpoints fix and inclusion
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
430
scripts/patch.sh
430
scripts/patch.sh
@@ -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"
|
||||
|
||||
188
scripts/test.sh
188
scripts/test.sh
@@ -1,29 +1,175 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# 1) Здоровье сервисов
|
||||
curl -sS http://localhost:8080/auth/health
|
||||
curl -sS http://localhost:8080/profiles/health
|
||||
# === Settings ===
|
||||
BASE_URL="${BASE_URL:-http://localhost:8080}"
|
||||
AUTH="$BASE_URL/auth"
|
||||
PROFILES="$BASE_URL/profiles"
|
||||
|
||||
# 2) Токен (любой юзер)
|
||||
curl -sS -X POST http://localhost:8080/auth/v1/token \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"admin@agency.dev","password":"secret123"}' | tee /tmp/token.json
|
||||
# Можно переопределить через окружение:
|
||||
EMAIL="${EMAIL:-user+$(date +%s)@example.com}"
|
||||
PASS="${PASS:-secret123}"
|
||||
FULL_NAME="${FULL_NAME:-Demo User}"
|
||||
ROLE="${ROLE:-CLIENT}"
|
||||
|
||||
ACCESS=$(python3 - <<'PY' /tmp/token.json
|
||||
import sys, json; print(json.load(open(sys.argv[1]))["access_token"])
|
||||
# Куда писать лог (по умолчанию в logs/)
|
||||
mkdir -p logs
|
||||
TS="$(date +%Y%m%d_%H%M%S)"
|
||||
LOG_FILE="${LOG_FILE:-logs/user_flow_${TS}.log}"
|
||||
|
||||
# Вспомогательные утилиты
|
||||
have_jq=0; command -v jq >/dev/null 2>&1 && have_jq=1
|
||||
|
||||
log() { echo "[$(date +'%F %T')] $*" | tee -a "$LOG_FILE" >&2; }
|
||||
hr() { printf -- '-----\n' | tee -a "$LOG_FILE" >/dev/null; }
|
||||
|
||||
# Выполнить HTTP и вернуть: <http_code>|<body_file>
|
||||
http_req() {
|
||||
local METHOD="$1"; shift
|
||||
local URL="$1"; shift
|
||||
local TOKEN="${1:-}"; shift || true
|
||||
local BODY="${1:-}"; shift || true
|
||||
|
||||
local RESP_FILE; RESP_FILE="$(mktemp)"
|
||||
local args=(-sS --connect-timeout 10 --max-time 30 -X "$METHOD" "$URL" -o "$RESP_FILE" -w "%{http_code}")
|
||||
[[ -n "$TOKEN" ]] && args+=(-H "Authorization: Bearer $TOKEN")
|
||||
[[ -n "$BODY" ]] && args+=(-H "Content-Type: application/json" -d "$BODY")
|
||||
|
||||
local CODE; CODE="$(curl "${args[@]}" || true)"
|
||||
echo "${CODE}|${RESP_FILE}"
|
||||
}
|
||||
|
||||
# Красиво положить ответ в лог
|
||||
log_response() {
|
||||
local title="$1"; shift
|
||||
local code="$1"; shift
|
||||
local body_file="$1"; shift
|
||||
|
||||
hr
|
||||
log "$title"
|
||||
log "URL: ${CURRENT_URL}"
|
||||
log "HTTP: ${code}"
|
||||
if [[ $have_jq -eq 1 ]]; then
|
||||
log "Body:"
|
||||
jq . "$body_file" 2>/dev/null | tee -a "$LOG_FILE" >/dev/null || cat "$body_file" | tee -a "$LOG_FILE" >/dev/null
|
||||
else
|
||||
log "Body (raw):"
|
||||
cat "$body_file" | tee -a "$LOG_FILE" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Достаём поле из JSON (без jq)
|
||||
json_get() {
|
||||
local file="$1"; shift
|
||||
local path="$1"; shift
|
||||
python3 - "$file" "$path" <<'PY'
|
||||
import sys, json
|
||||
fn, path = sys.argv[1], sys.argv[2]
|
||||
try:
|
||||
data = json.load(open(fn, 'rb'))
|
||||
except Exception:
|
||||
print('')
|
||||
sys.exit(0)
|
||||
cur = data
|
||||
for k in path.split('.'):
|
||||
if isinstance(cur, list):
|
||||
try:
|
||||
k = int(k)
|
||||
except Exception:
|
||||
print(''); sys.exit(0)
|
||||
if 0 <= k < len(cur):
|
||||
cur = cur[k]
|
||||
else:
|
||||
print(''); sys.exit(0)
|
||||
elif isinstance(cur, dict):
|
||||
cur = cur.get(k)
|
||||
else:
|
||||
print(''); sys.exit(0)
|
||||
if cur is None:
|
||||
print(''); sys.exit(0)
|
||||
if isinstance(cur, (dict, list)):
|
||||
print(json.dumps(cur))
|
||||
else:
|
||||
print(cur)
|
||||
PY
|
||||
)
|
||||
}
|
||||
|
||||
# 3) /me — ожидаемо 404 (если профиля нет), главное НЕ 401
|
||||
curl -i -sS http://localhost:8080/profiles/v1/profiles/me \
|
||||
-H "Authorization: Bearer $ACCESS"
|
||||
echo "== Log file: ${LOG_FILE} ==" >&2
|
||||
hr
|
||||
log "BASE_URL: ${BASE_URL}"
|
||||
log "EMAIL: ${EMAIL}"
|
||||
log "FULL_NAME: ${FULL_NAME}"
|
||||
log "ROLE: ${ROLE}"
|
||||
|
||||
# 4) Создать профиль — должно быть 201/200, без 500
|
||||
curl -i -sS -X POST http://localhost:8080/profiles/v1/profiles \
|
||||
-H "Authorization: Bearer $ACCESS" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"gender":"female","city":"Moscow","languages":["ru","en"],"interests":["music","travel"]}'
|
||||
# 1) REGISTER
|
||||
BODY_REG=$(printf '{"email":"%s","password":"%s","full_name":"%s","role":"%s"}' "$EMAIL" "$PASS" "$FULL_NAME" "$ROLE")
|
||||
CURRENT_URL="$AUTH/v1/register"
|
||||
resp="$(http_req POST "$CURRENT_URL" "" "$BODY_REG")"
|
||||
code="${resp%%|*}"; body="${resp##*|}"
|
||||
log_response "REGISTER" "$code" "$body"
|
||||
|
||||
# 5) Снова /me — теперь 200 с JSON (UUIDы как строки)
|
||||
curl -sS http://localhost:8080/profiles/v1/profiles/me \
|
||||
-H "Authorization: Bearer $ACCESS" | jq .
|
||||
if [[ "$code" != "201" && "$code" != "200" ]]; then
|
||||
# если уже существует — ок, продолжаем
|
||||
detail="$(json_get "$body" "detail" || true)"
|
||||
if [[ "$code" == "400" && "$detail" == "Email already in use" ]]; then
|
||||
log "Register: email already exists — continue to login"
|
||||
else
|
||||
log "Register non-success: ${code} — continue to login anyway"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2) LOGIN (TOKEN)
|
||||
BODY_LOGIN=$(printf '{"email":"%s","password":"%s"}' "$EMAIL" "$PASS")
|
||||
CURRENT_URL="$AUTH/v1/token"
|
||||
resp="$(http_req POST "$CURRENT_URL" "" "$BODY_LOGIN")"
|
||||
code="${resp%%|*}"; body="${resp##*|}"
|
||||
log_response "LOGIN / TOKEN" "$code" "$body"
|
||||
|
||||
if [[ "$code" != "200" ]]; then
|
||||
log "ERROR: login failed with $code"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACCESS_TOKEN="$(json_get "$body" "access_token")"
|
||||
if [[ -z "$ACCESS_TOKEN" ]]; then
|
||||
log "ERROR: access_token not found in login response"
|
||||
exit 1
|
||||
fi
|
||||
log "Got access_token (hidden)"
|
||||
hr
|
||||
|
||||
# 3) GET PROFILE (me)
|
||||
CURRENT_URL="$PROFILES/v1/profiles/me"
|
||||
resp="$(http_req GET "$CURRENT_URL" "$ACCESS_TOKEN")"
|
||||
code="${resp%%|*}"; body="${resp##*|}"
|
||||
log_response "GET /profiles/me" "$code" "$body"
|
||||
|
||||
if [[ "$code" == "404" ]]; then
|
||||
log "Profile not found — creating…"
|
||||
|
||||
# 3a) CREATE PROFILE (минимальный)
|
||||
BODY_CREATE='{"gender":"other","city":"Moscow","languages":["ru","en"],"interests":["music","travel"]}'
|
||||
CURRENT_URL="$PROFILES/v1/profiles"
|
||||
resp="$(http_req POST "$CURRENT_URL" "$ACCESS_TOKEN" "$BODY_CREATE")"
|
||||
code="${resp%%|*}"; body="${resp##*|}"
|
||||
log_response "CREATE /profiles" "$code" "$body"
|
||||
if [[ "$code" != "201" && "$code" != "200" ]]; then
|
||||
log "ERROR: create profile failed with $code"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3b) GET PROFILE AGAIN
|
||||
CURRENT_URL="$PROFILES/v1/profiles/me"
|
||||
resp="$(http_req GET "$CURRENT_URL" "$ACCESS_TOKEN")"
|
||||
code="${resp%%|*}"; body="${resp##*|}"
|
||||
log_response "GET /profiles/me (after create)" "$code" "$body"
|
||||
if [[ "$code" != "200" ]]; then
|
||||
log "ERROR: expected 200 after create, got $code"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "$code" != "200" ]]; then
|
||||
log "ERROR: unexpected code for /profiles/me: $code"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "DONE. Log saved to: ${LOG_FILE}"
|
||||
|
||||
Reference in New Issue
Block a user