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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,7 +17,7 @@ build/
|
||||
|
||||
# IDE / OS
|
||||
.DS_Store
|
||||
.idea/
|
||||
.idea/a
|
||||
.vscode/
|
||||
|
||||
# Docker
|
||||
|
||||
1
chat/src/app/api/chat.py
Normal file
1
chat/src/app/api/chat.py
Normal file
@@ -0,0 +1 @@
|
||||
'"$@"'
|
||||
454
logs/audit_20250810_145217.log
Normal file
454
logs/audit_20250810_145217.log
Normal file
@@ -0,0 +1,454 @@
|
||||
[14:52:17] OpenAPI: http://localhost:8080/openapi.json
|
||||
[14:52:17] ✔ OpenAPI fetched (32448 bytes)
|
||||
[14:52:17] Prepare tokens
|
||||
[14:52:17] REGISTER admin+20250810_145217@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
[14:52:17] TOKEN for admin+20250810_145217@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhY2Q5ZTQ5YS1jNTQ5LTQxZDUtODA0NC0zODZkODMzZmVlYjQiLCJlbWFpbCI6ImFkbWluKzIwMjUwODEwXzE0NTIxN0BhdWRpdC5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNjAzN30.dFVm89lOssfFuTI5MJWGTi4c6fVLc80sFZkbdg5o_SU","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhY2Q5ZTQ5YS1jNTQ5LTQxZDUtODA0NC0zODZkODMzZmVlYjQiLCJlbWFpbCI6ImFkbWluKzIwMjUwODEwXzE0NTIxN0BhdWRpdC5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6InJlZnJlc2giLCJ[14:52:18] REGISTER client+20250810_145217@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
[14:52:18] TOKEN for client+20250810_145217@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYzU5YTRiOC03YzA5LTRkMGQtODE3NC1kYjM1ZmRhNzAxOWYiLCJlbWFpbCI6ImNsaWVudCsyMDI1MDgxMF8xNDUyMTdAYXVkaXQuZGV2Iiwicm9sZSI6IkNMSUVOVCIsInR5cGUiOiJhY2Nlc3MiLCJleHAiOjE3NTQ4MDYwMzh9.JnJ-rl58Qtpf8PY-1OjhDjlrCJ66vR-nFO0tEQad7QM","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYzU5YTRiOC03YzA5LTRkMGQtODE3NC1kYjM1ZmRhNzAxOWYiLCJlbWFpbCI6ImNsaWVudCsyMDI1MDgxMF8xNDUyMTdAYXVkaXQuZGV2Iiwicm9sZSI6IkNMSUVOVCIsInR5cGUiOiJyZWZyZXNoIi[14:52:18] ✔ Tokens acquired
|
||||
[14:52:18] Analyze path‑parameters in schema…
|
||||
[14:52:18] ✖ Найдены операции без объявленных path‑параметров: 19
|
||||
[
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
[14:52:18] SMOKE missing-param operations…
|
||||
449
logs/audit_20250810_145252.json
Normal file
449
logs/audit_20250810_145252.json
Normal file
@@ -0,0 +1,449 @@
|
||||
{
|
||||
"base_url": "http://localhost:8080",
|
||||
"openapi_url": "http://localhost:8080/openapi.json",
|
||||
"ts": "20250810_145252",
|
||||
"summary": {
|
||||
"missing_path_param_ops": 19
|
||||
},
|
||||
"findings": {
|
||||
"missing_path_params": [
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
445
logs/audit_20250810_145252.log
Normal file
445
logs/audit_20250810_145252.log
Normal file
@@ -0,0 +1,445 @@
|
||||
[14:52:52] OpenAPI: http://localhost:8080/openapi.json
|
||||
[14:52:52] ✔ OpenAPI fetched (32448 bytes)
|
||||
[14:52:52] Analyze path‑parameters in schema…
|
||||
[14:52:52] ✖ Найдены операции без объявленных path‑параметров: 19
|
||||
[
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
[14:52:52] Report: logs/audit_20250810_145252.json
|
||||
[14:52:52] Log: logs/audit_20250810_145252.log
|
||||
449
logs/audit_20250810_145805.json
Normal file
449
logs/audit_20250810_145805.json
Normal file
@@ -0,0 +1,449 @@
|
||||
{
|
||||
"base_url": "http://localhost:8080",
|
||||
"openapi_url": "http://localhost:8080/openapi.json",
|
||||
"ts": "20250810_145805",
|
||||
"summary": {
|
||||
"missing_path_param_ops": 19
|
||||
},
|
||||
"findings": {
|
||||
"missing_path_params": [
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
494
logs/audit_20250810_145805.log
Normal file
494
logs/audit_20250810_145805.log
Normal file
@@ -0,0 +1,494 @@
|
||||
[14:58:05] OpenAPI: http://localhost:8080/openapi.json
|
||||
[14:58:05] ✔ OpenAPI fetched (32448 bytes)
|
||||
[14:58:05] Prepare tokens
|
||||
[14:58:05] REGISTER admin+20250810_145805@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
[14:58:05] TOKEN for admin+20250810_145805@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NWEzMDU2Mi0yZWZkLTQwODgtODljNi1mZDViYTE2NzlhMGEiLCJlbWFpbCI6ImFkbWluKzIwMjUwODEwXzE0NTgwNUBhdWRpdC5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNjM4NX0.jpBrPjuIurUlY2FzmOjOeiH82Y0OuujKxK10uykHPWc","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NWEzMDU2Mi0yZWZkLTQwODgtODljNi1mZDViYTE2NzlhMGEiLCJlbWFpbCI6ImFkbWluKzIwMjUwODEwXzE0NTgwNUBhdWRpdC5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6InJlZnJlc2giLCJ[14:58:05] REGISTER client+20250810_145805@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
[14:58:06] TOKEN for client+20250810_145805@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ODA2YWExMS0xYzQ0LTRjZWMtYjA1Ni1kOWViMjk2ZjNiN2MiLCJlbWFpbCI6ImNsaWVudCsyMDI1MDgxMF8xNDU4MDVAYXVkaXQuZGV2Iiwicm9sZSI6IkNMSUVOVCIsInR5cGUiOiJhY2Nlc3MiLCJleHAiOjE3NTQ4MDYzODZ9.LIs31GuKJTlwWO9L5jCWj6CgQ_cX9go4r4WCtKC5LDc","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ODA2YWExMS0xYzQ0LTRjZWMtYjA1Ni1kOWViMjk2ZjNiN2MiLCJlbWFpbCI6ImNsaWVudCsyMDI1MDgxMF8xNDU4MDVAYXVkaXQuZGV2Iiwicm9sZSI6IkNMSUVOVCIsInR5cGUiOiJyZWZyZXNoIi[14:58:06] ✔ Tokens acquired
|
||||
[14:58:06] Analyze path‑parameters in schema…
|
||||
[14:58:06] ✖ Найдены операции без объявленных path‑параметров: 19
|
||||
[
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
[14:58:06] SMOKE missing-param operations…
|
||||
[14:58:06] → get http://localhost:8080/auth/v1/users/{user_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 403
|
||||
{"detail":"Insufficient role"}[14:58:06] → delete http://localhost:8080/auth/v1/users/{user_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 403
|
||||
{"detail":"Insufficient role"}[14:58:06] → patch http://localhost:8080/auth/v1/users/{user_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 403
|
||||
{"detail":"Insufficient role"}[14:58:06] → get http://localhost:8080/profiles/v1/profiles/{profile_id} (security: [{HTTPBearer:[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[14:58:06] → get http://localhost:8080/profiles/v1/profiles/by-user/{user_id} (security: [{HTTPBearer:[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[14:58:06] → put http://localhost:8080/profiles/v1/likes/{target_user_id} (security: [{HTTPBearer:[]}])
|
||||
HTTP: 204
|
||||
[14:58:06] → delete http://localhost:8080/profiles/v1/likes/{target_user_id} (security: [{HTTPBearer:[]}])
|
||||
HTTP: 204
|
||||
[14:58:06] → get http://localhost:8080/match/v1/pairs/{pair_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[14:58:06] → delete http://localhost:8080/match/v1/pairs/{pair_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 403
|
||||
{"detail":"Insufficient role"}[14:58:06] → patch http://localhost:8080/match/v1/pairs/{pair_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 403
|
||||
{"detail":"Insufficient role"}[14:58:06] → post http://localhost:8080/match/v1/pairs/{pair_id}/accept (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[14:58:06] → post http://localhost:8080/match/v1/pairs/{pair_id}/reject (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[14:58:06] → get http://localhost:8080/chat/v1/rooms/{room_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[14:58:06] → get http://localhost:8080/chat/v1/rooms/{room_id}/messages (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[14:58:06] → post http://localhost:8080/chat/v1/rooms/{room_id}/messages (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 422
|
||||
{"detail":[{"type":"missing","loc":["body"],"msg":"Field required","input":null}]}[14:58:06] → get http://localhost:8080/payments/v1/invoices/{inv_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[14:58:06] → delete http://localhost:8080/payments/v1/invoices/{inv_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 403
|
||||
{"detail":"Insufficient role"}[14:58:06] → patch http://localhost:8080/payments/v1/invoices/{inv_id} (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 403
|
||||
{"detail":"Insufficient role"}[14:58:06] → post http://localhost:8080/payments/v1/invoices/{inv_id}/mark-paid (security: [{OAuth2PasswordBearer:[]}])
|
||||
HTTP: 403
|
||||
{"detail":"Insufficient role"}[14:58:06] Report: logs/audit_20250810_145805.json
|
||||
[14:58:06] Log: logs/audit_20250810_145805.log
|
||||
449
logs/audit_20250810_150332.json
Normal file
449
logs/audit_20250810_150332.json
Normal file
@@ -0,0 +1,449 @@
|
||||
{
|
||||
"base_url": "http://localhost:8080",
|
||||
"openapi_url": "http://localhost:8080/openapi.json",
|
||||
"ts": "20250810_150332",
|
||||
"summary": {
|
||||
"missing_path_param_ops": 19
|
||||
},
|
||||
"findings": {
|
||||
"missing_path_params": [
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
494
logs/audit_20250810_150332.log
Normal file
494
logs/audit_20250810_150332.log
Normal file
@@ -0,0 +1,494 @@
|
||||
[15:03:32] OpenAPI: http://localhost:8080/openapi.json
|
||||
[15:03:32] ✔ OpenAPI fetched (32448 bytes)
|
||||
[15:03:32] Prepare tokens
|
||||
[15:03:32] REGISTER admin+20250810_150332@audit.dev (role=ADMIN) → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
[15:03:32] TOKEN for admin+20250810_150332@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxN2M5ODNjZi02NzQ2LTRiZDctYTU2Yi01NGNmMWMxZGY4M2MiLCJlbWFpbCI6ImFkbWluKzIwMjUwODEwXzE1MDMzMkBhdWRpdC5kZXYiLCJyb2xlIjoiQURNSU4iLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0ODA2NzEyfQ.hvizRRRhsJ3TwXB59kEDWsD5HopKdlnZcLN1NVowtXc","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxN2M5ODNjZi02NzQ2LTRiZDctYTU2Yi01NGNmMWMxZGY4M2MiLCJl[15:03:33] REGISTER client+20250810_150332@audit.dev (role=CLIENT) → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
[15:03:33] TOKEN for client+20250810_150332@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiMDUxOTUyNi03MDMyLTRiNTEtODRkOS1mNWY2OWY2ODBkMTgiLCJlbWFpbCI6ImNsaWVudCsyMDI1MDgxMF8xNTAzMzJAYXVkaXQuZGV2Iiwicm9sZSI6IkNMSUVOVCIsInR5cGUiOiJhY2Nlc3MiLCJleHAiOjE3NTQ4MDY3MTN9.qDEzkMrlcIfoJCztZdnBXNjc-LIse7mlqWvfz8IeyrQ","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiMDUxOTUyNi03MDMyLTRiNTEtODRkOS1mNWY2OWY2ODBkMTgiLC[15:03:33] ✔ Tokens acquired
|
||||
[15:03:33] Analyze path‑parameters in schema…
|
||||
[15:03:33] ✖ Найдены операции без объявленных path‑параметров: 19
|
||||
[
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
[15:03:33] SMOKE missing-param operations…
|
||||
[15:03:33] → get http://localhost:8080/auth/v1/users/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"User not found"}[15:03:33] → delete http://localhost:8080/auth/v1/users/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 204
|
||||
[15:03:33] → patch http://localhost:8080/auth/v1/users/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 422
|
||||
{"detail":[{"type":"missing","loc":["body"],"msg":"Field required","input":null}]}[15:03:33] → get http://localhost:8080/profiles/v1/profiles/00000000-0000-0000-0000-000000000000 (security: [{"HTTPBearer":[]}])
|
||||
HTTP: 500
|
||||
Internal Server Error[15:03:33] → get http://localhost:8080/profiles/v1/profiles/by-user/00000000-0000-0000-0000-000000000000 (security: [{"HTTPBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Not found"}[15:03:33] → put http://localhost:8080/profiles/v1/likes/00000000-0000-0000-0000-000000000000 (security: [{"HTTPBearer":[]}])
|
||||
HTTP: 204
|
||||
[15:03:33] → delete http://localhost:8080/profiles/v1/likes/00000000-0000-0000-0000-000000000000 (security: [{"HTTPBearer":[]}])
|
||||
HTTP: 204
|
||||
[15:03:33] → get http://localhost:8080/match/v1/pairs/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Not found"}[15:03:33] → delete http://localhost:8080/match/v1/pairs/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 204
|
||||
[15:03:33] → patch http://localhost:8080/match/v1/pairs/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 422
|
||||
{"detail":[{"type":"missing","loc":["body"],"msg":"Field required","input":null}]}[15:03:33] → post http://localhost:8080/match/v1/pairs/00000000-0000-0000-0000-000000000000/accept (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Not found"}[15:03:33] → post http://localhost:8080/match/v1/pairs/00000000-0000-0000-0000-000000000000/reject (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Not found"}[15:03:33] → get http://localhost:8080/chat/v1/rooms/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Not found"}[15:03:33] → get http://localhost:8080/chat/v1/rooms/00000000-0000-0000-0000-000000000000/messages (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Room not found"}[15:03:33] → post http://localhost:8080/chat/v1/rooms/00000000-0000-0000-0000-000000000000/messages (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Room not found"}[15:03:33] → get http://localhost:8080/payments/v1/invoices/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Not found"}[15:03:33] → delete http://localhost:8080/payments/v1/invoices/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 204
|
||||
[15:03:33] → patch http://localhost:8080/payments/v1/invoices/00000000-0000-0000-0000-000000000000 (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 422
|
||||
{"detail":[{"type":"missing","loc":["body"],"msg":"Field required","input":null}]}[15:03:33] → post http://localhost:8080/payments/v1/invoices/00000000-0000-0000-0000-000000000000/mark-paid (security: [{"OAuth2PasswordBearer":[]}])
|
||||
HTTP: 404
|
||||
{"detail":"Not found"}[15:03:33] Report: logs/audit_20250810_150332.json
|
||||
[15:03:33] Log: logs/audit_20250810_150332.log
|
||||
449
logs/audit_20250810_150348.json
Normal file
449
logs/audit_20250810_150348.json
Normal file
@@ -0,0 +1,449 @@
|
||||
{
|
||||
"base_url": "http://localhost:8080",
|
||||
"openapi_url": "http://localhost:8080/openapi.json",
|
||||
"ts": "20250810_150348",
|
||||
"summary": {
|
||||
"missing_path_param_ops": 19
|
||||
},
|
||||
"findings": {
|
||||
"missing_path_params": [
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
445
logs/audit_20250810_150348.log
Normal file
445
logs/audit_20250810_150348.log
Normal file
@@ -0,0 +1,445 @@
|
||||
[15:03:48] OpenAPI: http://localhost:8080/openapi.json
|
||||
[15:03:48] ✔ OpenAPI fetched (32448 bytes)
|
||||
[15:03:48] Analyze path‑parameters in schema…
|
||||
[15:03:48] ✖ Найдены операции без объявленных path‑параметров: 19
|
||||
[
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_user_v1_users__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_user_v1_users__user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/auth/v1/users/{user_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_user_v1_users__user_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/{profile_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"profile_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"profile_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_profile_v1_profiles__profile_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/profiles/by-user/{user_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"user_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_by_user_v1_profiles_by_user__user_id__get",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "put",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "put_like_v1_likes__target_user_id__put",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/profiles/v1/likes/{target_user_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"target_user_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"target_user_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_like_v1_likes__target_user_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_pair_v1_pairs__pair_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_pair_v1_pairs__pair_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_pair_v1_pairs__pair_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/accept",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "accept_v1_pairs__pair_id__accept_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/match/v1/pairs/{pair_id}/reject",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"pair_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"pair_id"
|
||||
]
|
||||
],
|
||||
"opId": "reject_v1_pairs__pair_id__reject_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_room_v1_rooms__room_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "list_messages_v1_rooms__room_id__messages_get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/chat/v1/rooms/{room_id}/messages",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"room_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"room_id"
|
||||
]
|
||||
],
|
||||
"opId": "send_message_v1_rooms__room_id__messages_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "get",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "get_invoice_v1_invoices__inv_id__get",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "delete",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "delete_invoice_v1_invoices__inv_id__delete",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}",
|
||||
"method": "patch",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "update_invoice_v1_invoices__inv_id__patch",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/payments/v1/invoices/{inv_id}/mark-paid",
|
||||
"method": "post",
|
||||
"needed": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"defined": [
|
||||
"inv_id"
|
||||
],
|
||||
"missing": [
|
||||
[
|
||||
"inv_id"
|
||||
]
|
||||
],
|
||||
"opId": "mark_paid_v1_invoices__inv_id__mark_paid_post",
|
||||
"security": [
|
||||
{
|
||||
"OAuth2PasswordBearer": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
[15:03:48] Report: logs/audit_20250810_150348.json
|
||||
[15:03:48] Log: logs/audit_20250810_150348.log
|
||||
11
logs/audit_20250810_153547.json
Normal file
11
logs/audit_20250810_153547.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"base_url": "http://localhost:8080",
|
||||
"openapi_url": "http://localhost:8080/openapi.json",
|
||||
"ts": "20250810_153547",
|
||||
"summary": {
|
||||
"missing_path_param_ops": 0
|
||||
},
|
||||
"findings": {
|
||||
"missing_path_params": []
|
||||
}
|
||||
}
|
||||
16
logs/audit_20250810_153547.log
Normal file
16
logs/audit_20250810_153547.log
Normal file
@@ -0,0 +1,16 @@
|
||||
[15:35:47] OpenAPI: http://localhost:8080/openapi.json
|
||||
[15:35:47] ✔ OpenAPI fetched (32448 bytes)
|
||||
[15:35:47] Prepare tokens
|
||||
[15:35:48] REGISTER admin+20250810_153547@audit.dev (role=ADMIN) → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
[15:35:48] TOKEN for admin+20250810_153547@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0YjczYjYxMy0wYTFlLTRkODMtYjM3NC1hYWY5OWY4ODVmZDQiLCJlbWFpbCI6ImFkbWluKzIwMjUwODEwXzE1MzU0N0BhdWRpdC5kZXYiLCJyb2xlIjoiQURNSU4iLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0ODA4NjQ4fQ.7AAL-8NVK3NiJaN0y4iUeZvYdvWnym05R-L9zeqwMr8","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0YjczYjYxMy0wYTFlLTRkODMtYjM3NC1hYWY5OWY4ODVmZDQiLCJl[15:35:48] REGISTER client+20250810_153547@audit.dev (role=CLIENT) → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
[15:35:48] TOKEN for client+20250810_153547@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMzFjZGFjOS1kYmZjLTQ0NTMtYWVkZi01Y2Q2YWJkZDFhYjciLCJlbWFpbCI6ImNsaWVudCsyMDI1MDgxMF8xNTM1NDdAYXVkaXQuZGV2Iiwicm9sZSI6IkNMSUVOVCIsInR5cGUiOiJhY2Nlc3MiLCJleHAiOjE3NTQ4MDg2NDh9.xRQXWYLDKsgRfvQRc_GN4bEnLsTgctQKAiXImJJsnxE","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMzFjZGFjOS1kYmZjLTQ0NTMtYWVkZi01Y2Q2YWJkZDFhYjciLC[15:35:48] ✔ Tokens acquired
|
||||
[15:35:48] Analyze path‑parameters in schema…
|
||||
[15:35:48] ✔ Проблем с path‑параметрами в схеме не найдено.
|
||||
[15:35:48] Report: logs/audit_20250810_153547.json
|
||||
[15:35:48] Log: logs/audit_20250810_153547.log
|
||||
11
logs/openapi_audit_20250810_143846.log
Normal file
11
logs/openapi_audit_20250810_143846.log
Normal file
@@ -0,0 +1,11 @@
|
||||
[0;36m[14:38:46][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m No missing path-param definitions in schema.
|
||||
[0;36m[14:38:46][0m Prepare tokens
|
||||
[0;36m[14:38:46][0m REGISTER admin+1754804326@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"b661b2ce-8109-4481-b126-0ac4ab4712dc","email":"admin+1754804326@audit.dev","full_name":"Audit Admin","role":"ADMIN","is_active":true}[0;36m[14:38:46][0m REGISTER client+1754804326@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"ad3ddf95-3bf4-4de0-9c01-d8bfa7e9afaa","email":"client+1754804326@audit.dev","full_name":"Audit Client","role":"CLIENT","is_active":true}[0;36m[14:38:46][0m TOKEN for admin+1754804326@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiNjYxYjJjZS04MTA5LTQ0ODEtYjEyNi0wYWM0YWI0NzEyZGMiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQzMjZAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNTIyN30.qekpbC4oywTg6GUsZrwLkLi6InadqlAyg9IlXSWiuy8","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiNjYxYjJjZS04MTA5LTQ0ODEtYjEyNi0wYWM0YWI0NzEyZGMiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQzMjZAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTYzMjd9.fgzpO2kGqQIDjZpddufaoZwNliSIKs78MOOs6vEuxQA","token_type":"bearer"}200|/tmp/tmp.MQwQIiPD4R/body_1754804327177054908.json✖[0m Empty token in response
|
||||
11
logs/openapi_audit_20250810_143930.log
Normal file
11
logs/openapi_audit_20250810_143930.log
Normal file
@@ -0,0 +1,11 @@
|
||||
[0;36m[14:39:30][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m No missing path-param definitions in schema.
|
||||
[0;36m[14:39:30][0m Prepare tokens
|
||||
[0;36m[14:39:30][0m REGISTER admin+1754804370@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"d895702e-003e-4c76-b881-3dd9013ad718","email":"admin+1754804370@audit.dev","full_name":"Audit Admin","role":"ADMIN","is_active":true}[0;36m[14:39:31][0m REGISTER client+1754804370@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"9de89380-7dc1-4c9b-a725-6b3892dd4d86","email":"client+1754804370@audit.dev","full_name":"Audit Client","role":"CLIENT","is_active":true}[0;36m[14:39:31][0m TOKEN for admin+1754804370@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkODk1NzAyZS0wMDNlLTRjNzYtYjg4MS0zZGQ5MDEzYWQ3MTgiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQzNzBAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNTI3MX0.ctbEhzmwPRc_1nyxc4-SYohdTmM__H2Afe4csjktamQ","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkODk1NzAyZS0wMDNlLTRjNzYtYjg4MS0zZGQ5MDEzYWQ3MTgiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQzNzBAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTYzNzF9.h1tbqmU75s4IveybFQJTZB6cx5O-HdRr8QDS5ufcdgs","token_type":"bearer"}200|/tmp/tmp.domipoILvA/body_1754804371694718750.json✖[0m Empty token in response
|
||||
11
logs/openapi_audit_20250810_143952.log
Normal file
11
logs/openapi_audit_20250810_143952.log
Normal file
@@ -0,0 +1,11 @@
|
||||
[0;36m[14:39:52][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m No missing path-param definitions in schema.
|
||||
[0;36m[14:39:52][0m Prepare tokens
|
||||
[0;36m[14:39:52][0m REGISTER admin+1754804392@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"4ea8cfdf-411a-4156-a708-20e1f937c0c7","email":"admin+1754804392@audit.dev","full_name":"Audit Admin","role":"ADMIN","is_active":true}[0;36m[14:39:52][0m REGISTER client+1754804392@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"606ec141-5a41-4ae4-a01d-0e271dca5e76","email":"client+1754804392@audit.dev","full_name":"Audit Client","role":"CLIENT","is_active":true}[0;36m[14:39:52][0m TOKEN for admin+1754804392@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0ZWE4Y2ZkZi00MTFhLTQxNTYtYTcwOC0yMGUxZjkzN2MwYzciLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQzOTJAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNTI5M30.bMvuxJyAHV9xQT49bQlcndEacujplwvQIwKc2LBcu8E","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0ZWE4Y2ZkZi00MTFhLTQxNTYtYTcwOC0yMGUxZjkzN2MwYzciLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQzOTJAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTYzOTN9.fOLyAp8-JLsF5ntSvcvbmbarYPlH33xJJzBIqOtoHj8","token_type":"bearer"}200|/tmp/tmp.8a4EKh0yJd/body_1754804393153931372.json✖[0m Empty token in response
|
||||
11
logs/openapi_audit_20250810_144126.log
Normal file
11
logs/openapi_audit_20250810_144126.log
Normal file
@@ -0,0 +1,11 @@
|
||||
[0;36m[14:41:26][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m Проблем с path‑параметрами в схеме не найдено.
|
||||
[0;36m[14:41:26][0m Prepare tokens
|
||||
[0;36m[14:41:26][0m REGISTER admin+1754804486@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"fa614a25-1a90-4219-8d99-328ccb6261af","email":"admin+1754804486@audit.dev","full_name":"Audit Admin","role":"ADMIN","is_active":true}[0;36m[14:41:26][0m REGISTER client+1754804486@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"b7aca4cd-7321-4666-83f4-6dcd2f41d4e4","email":"client+1754804486@audit.dev","full_name":"Audit Client","role":"CLIENT","is_active":true}[0;36m[14:41:26][0m TOKEN for admin+1754804486@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmYTYxNGEyNS0xYTkwLTQyMTktOGQ5OS0zMjhjY2I2MjYxYWYiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ0ODZAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNTM4Nn0.GB4lBfmEzpkXJ6N51URV4RR1w4096jdL3YSwTxzlF2M","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmYTYxNGEyNS0xYTkwLTQyMTktOGQ5OS0zMjhjY2I2MjYxYWYiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ0ODZAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTY0ODZ9.-JhPKTCY0t3SpQmoLA6iKYadisMyESrn-rWu1yvV6lg","token_type":"bearer"}200|/tmp/tmp.IxGgVqPsBj/h_1754804486719735758.txt|/tmp/tmp.IxGgVqPsBj/b_1754804486720782176.txt✖[0m Empty access_token in response. See body above.
|
||||
10
logs/openapi_audit_20250810_144251.log
Normal file
10
logs/openapi_audit_20250810_144251.log
Normal file
@@ -0,0 +1,10 @@
|
||||
[0;36m[14:42:51][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m Проблем с path‑параметрами в схеме не найдено.
|
||||
[0;36m[14:42:51][0m REGISTER admin+1754804571@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"5159a9c9-2cb2-439e-8fba-f074df6f3f62","email":"admin+1754804571@audit.dev","full_name":"Audit ADMIN","role":"ADMIN","is_active":true}[0;36m[14:42:51][0m REGISTER client+1754804571@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"0a1261da-cd58-45d2-b2ac-ba1cbca3ed04","email":"client+1754804571@audit.dev","full_name":"Audit CLIENT","role":"CLIENT","is_active":true}[0;36m[14:42:51][0m TOKEN for admin+1754804571@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1MTU5YTljOS0yY2IyLTQzOWUtOGZiYS1mMDc0ZGY2ZjNmNjIiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ1NzFAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNTQ3Mn0.tNxZJFWwd_z-6gPc8WD05_EaB5-ZR-OhHjzqC84-rzI","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1MTU5YTljOS0yY2IyLTQzOWUtOGZiYS1mMDc0ZGY2ZjNmNjIiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ1NzFAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTY1NzJ9.kPQdnZArXVW0YlhJLcjfSLDz0bZkNanVuv_ORofjZHw","token_type":"bearer"}[0;31m✖[0m Empty access_token in response
|
||||
10
logs/openapi_audit_20250810_144444.log
Normal file
10
logs/openapi_audit_20250810_144444.log
Normal file
@@ -0,0 +1,10 @@
|
||||
[0;36m[14:44:45][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m Проблем с path‑параметрами в схеме не найдено.
|
||||
[0;36m[14:44:45][0m REGISTER admin+1754804685@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"240ec6b3-f808-4c7f-b34e-94ef2d116d02","email":"admin+1754804685@audit.dev","full_name":"Audit ADMIN","role":"ADMIN","is_active":true}[0;36m[14:44:45][0m REGISTER client+1754804685@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"7bbcf608-e287-47bb-b764-f9a8a6dd51ef","email":"client+1754804685@audit.dev","full_name":"Audit CLIENT","role":"CLIENT","is_active":true}[0;36m[14:44:45][0m TOKEN for admin+1754804685@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyNDBlYzZiMy1mODA4LTRjN2YtYjM0ZS05NGVmMmQxMTZkMDIiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ2ODVAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNTU4NX0.Dqpib2vUHp01UG9fnzpTheJFlHx7YSIzb-Nos8mDZiA","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyNDBlYzZiMy1mODA4LTRjN2YtYjM0ZS05NGVmMmQxMTZkMDIiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ2ODVAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTY2ODV9.6oaupNjTprbAuJ3IodqJJePgoOUpiB-hsVYYKc94mdE","token_type":"bearer"}[0;31m✖[0m Empty access_token in response
|
||||
14
logs/openapi_audit_20250810_144749.log
Normal file
14
logs/openapi_audit_20250810_144749.log
Normal file
@@ -0,0 +1,14 @@
|
||||
[0;36m[14:47:49][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m Проблем с path‑параметрами в схеме не найдено.
|
||||
[0;36m[14:47:49][0m REGISTER admin+1754804869@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"e59260d0-9b68-4b34-bb99-79f2388db828","email":"admin+1754804869@audit.dev","full_name":"Audit ADMIN","role":"ADMIN","is_active":true}[0;36m[14:47:49][0m REGISTER client+1754804869@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"24fc407d-3b73-4f43-aec9-75a5acb43957","email":"client+1754804869@audit.dev","full_name":"Audit CLIENT","role":"CLIENT","is_active":true}[0;36m[14:47:50][0m TOKEN for admin+1754804869@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNTkyNjBkMC05YjY4LTRiMzQtYmI5OS03OWYyMzg4ZGI4MjgiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ4NjlAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNTc3MH0.RLw5GBq3ti79qdt3FTvMCcL4J_wDhR-UDZatVYTJrbo","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNTkyNjBkMC05YjY4LTRiMzQtYmI5OS03OWYyMzg4ZGI4MjgiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ4NjlAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTY4NzB9.YkAPwLUHuIjz40-R-OGe7RqxD3PpfnP9qf90AFap-Yk","token_type":"bearer"}
|
||||
[0;36m[14:47:50][0m TOKEN for client+1754804869@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyNGZjNDA3ZC0zYjczLTRmNDMtYWVjOS03NWE1YWNiNDM5NTciLCJlbWFpbCI6ImNsaWVudCsxNzU0ODA0ODY5QGF1ZGl0LmRldiIsInJvbGUiOiJDTElFTlQiLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0ODA1NzcwfQ.hbPMN71FWv7E1lORHVwIO-i0E92wiV6wvrvv-s2JeQU","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyNGZjNDA3ZC0zYjczLTRmNDMtYWVjOS03NWE1YWNiNDM5NTciLCJlbWFpbCI6ImNsaWVudCsxNzU0ODA0ODY5QGF1ZGl0LmRldiIsInJvbGUiOiJDTElFTlQiLCJ0eXBlIjoicmVmcmVzaCIsImV4cCI6MTc1NzM5Njg3MH0.5rHQJ-11xduNN5RZ-qVc0eFn6usQsCdDaPNsUZU2zwQ","token_type":"bearer"}
|
||||
[0;32m✔[0m Tokens acquired
|
||||
14
logs/openapi_audit_20250810_144935.log
Normal file
14
logs/openapi_audit_20250810_144935.log
Normal file
@@ -0,0 +1,14 @@
|
||||
[0;36m[14:49:35][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m Проблем с path‑параметрами в схеме не найдено.
|
||||
[0;36m[14:49:35][0m REGISTER admin+1754804975@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"379e9211-32ec-4135-9e82-1f550e4e1269","email":"admin+1754804975@audit.dev","full_name":"Audit ADMIN","role":"ADMIN","is_active":true}[0;36m[14:49:35][0m REGISTER client+1754804975@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"6ca28dcd-f7c2-4a60-bed3-57b412b00404","email":"client+1754804975@audit.dev","full_name":"Audit CLIENT","role":"CLIENT","is_active":true}[0;36m[14:49:36][0m TOKEN for admin+1754804975@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNzllOTIxMS0zMmVjLTQxMzUtOWU4Mi0xZjU1MGU0ZTEyNjkiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ5NzVAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwNTg3Nn0.qjZ1pkegHeGwN5mXez1G6PzDoSmRnTiYzVjYbxcDJ2g","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNzllOTIxMS0zMmVjLTQxMzUtOWU4Mi0xZjU1MGU0ZTEyNjkiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDQ5NzVAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTY5NzZ9.Hyg05Y_W-oSpvvC0vyJ_hECSQEE0nQv6VniZQvX4yH8","token_type":"bearer"}
|
||||
[0;36m[14:49:36][0m TOKEN for client+1754804975@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2Y2EyOGRjZC1mN2MyLTRhNjAtYmVkMy01N2I0MTJiMDA0MDQiLCJlbWFpbCI6ImNsaWVudCsxNzU0ODA0OTc1QGF1ZGl0LmRldiIsInJvbGUiOiJDTElFTlQiLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0ODA1ODc2fQ.iKXVOwmi3kTQsK2DyDtgR4vCL4ASDF7t3Oo9oDSCtuA","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2Y2EyOGRjZC1mN2MyLTRhNjAtYmVkMy01N2I0MTJiMDA0MDQiLCJlbWFpbCI6ImNsaWVudCsxNzU0ODA0OTc1QGF1ZGl0LmRldiIsInJvbGUiOiJDTElFTlQiLCJ0eXBlIjoicmVmcmVzaCIsImV4cCI6MTc1NzM5Njk3Nn0.z7N5eYxIO3pR9Y99x44_tHDQ1zBLa8vQop1Rsz1Kg-A","token_type":"bearer"}
|
||||
[0;32m✔[0m Tokens acquired
|
||||
14
logs/openapi_audit_20250810_153553.log
Normal file
14
logs/openapi_audit_20250810_153553.log
Normal file
@@ -0,0 +1,14 @@
|
||||
[0;36m[15:35:53][0m OPENAPI: http://localhost:8080/openapi.json
|
||||
[0;32m✔[0m OpenAPI fetched (32448 bytes)
|
||||
[0;32m✔[0m Проблем с path‑параметрами в схеме не найдено.
|
||||
[0;36m[15:35:53][0m REGISTER admin+1754807753@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"8913ac48-c289-43b2-87db-a37aa5e39548","email":"admin+1754807753@audit.dev","full_name":"Audit ADMIN","role":"ADMIN","is_active":true}[0;36m[15:35:54][0m REGISTER client+1754807753@audit.dev → http://localhost:8080/auth/v1/register
|
||||
HTTP: 201
|
||||
{"id":"0f4a02da-d0fa-494e-abbf-a2968ad44769","email":"client+1754807753@audit.dev","full_name":"Audit CLIENT","role":"CLIENT","is_active":true}[0;36m[15:35:54][0m TOKEN for admin+1754807753@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4OTEzYWM0OC1jMjg5LTQzYjItODdkYi1hMzdhYTVlMzk1NDgiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDc3NTNAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDgwODY1NH0.YRh6bpLSSdGVDPqnSX4pEKPOshqqSjBNi54LUEICI70","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4OTEzYWM0OC1jMjg5LTQzYjItODdkYi1hMzdhYTVlMzk1NDgiLCJlbWFpbCI6ImFkbWluKzE3NTQ4MDc3NTNAYXVkaXQuZGV2Iiwicm9sZSI6IkFETUlOIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTczOTk3NTR9.HGPD33LyxA32Q6o_Vf93D8nUfqxPKuT9hxZXawptXO8","token_type":"bearer"}
|
||||
[0;36m[15:35:54][0m TOKEN for client+1754807753@audit.dev → http://localhost:8080/auth/v1/token
|
||||
HTTP: 200
|
||||
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwZjRhMDJkYS1kMGZhLTQ5NGUtYWJiZi1hMjk2OGFkNDQ3NjkiLCJlbWFpbCI6ImNsaWVudCsxNzU0ODA3NzUzQGF1ZGl0LmRldiIsInJvbGUiOiJDTElFTlQiLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0ODA4NjU0fQ.o0q7ADkiX8uyfd_5U_A5ikcjDWKGOZyxA7Qn5Mkg3zY","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwZjRhMDJkYS1kMGZhLTQ5NGUtYWJiZi1hMjk2OGFkNDQ3NjkiLCJlbWFpbCI6ImNsaWVudCsxNzU0ODA3NzUzQGF1ZGl0LmRldiIsInJvbGUiOiJDTElFTlQiLCJ0eXBlIjoicmVmcmVzaCIsImV4cCI6MTc1NzM5OTc1NH0.rdHhue9u-Xe-xq2EdQvV2fc0FuYV6CiVv7BXIW56fY4","token_type":"bearer"}
|
||||
[0;32m✔[0m Tokens acquired
|
||||
79
logs/user_flow_20250810_142009.log
Normal file
79
logs/user_flow_20250810_142009.log
Normal file
@@ -0,0 +1,79 @@
|
||||
-----
|
||||
[2025-08-10 14:20:09] BASE_URL: http://localhost:8080
|
||||
[2025-08-10 14:20:09] EMAIL: user+1754803209@example.com
|
||||
[2025-08-10 14:20:09] FULL_NAME: Demo User
|
||||
[2025-08-10 14:20:09] ROLE: CLIENT
|
||||
-----
|
||||
[2025-08-10 14:20:09] REGISTER
|
||||
[2025-08-10 14:20:09] URL: http://localhost:8080/auth/v1/register
|
||||
[2025-08-10 14:20:09] HTTP: 201
|
||||
[2025-08-10 14:20:09] Body:
|
||||
{
|
||||
"id": "f4ffd570-5f43-4268-ae6c-cf3f8ec57793",
|
||||
"email": "user+1754803209@example.com",
|
||||
"full_name": "Demo User",
|
||||
"role": "CLIENT",
|
||||
"is_active": true
|
||||
}
|
||||
-----
|
||||
[2025-08-10 14:20:09] LOGIN / TOKEN
|
||||
[2025-08-10 14:20:09] URL: http://localhost:8080/auth/v1/token
|
||||
[2025-08-10 14:20:09] HTTP: 200
|
||||
[2025-08-10 14:20:09] Body:
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmNGZmZDU3MC01ZjQzLTQyNjgtYWU2Yy1jZjNmOGVjNTc3OTMiLCJlbWFpbCI6InVzZXIrMTc1NDgwMzIwOUBleGFtcGxlLmNvbSIsInJvbGUiOiJDTElFTlQiLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0ODA0MTA5fQ.HgFmBf7lVPTFAiS91k-Ezh5DDjbdFCcMX9hdxY6-YcM",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmNGZmZDU3MC01ZjQzLTQyNjgtYWU2Yy1jZjNmOGVjNTc3OTMiLCJlbWFpbCI6InVzZXIrMTc1NDgwMzIwOUBleGFtcGxlLmNvbSIsInJvbGUiOiJDTElFTlQiLCJ0eXBlIjoicmVmcmVzaCIsImV4cCI6MTc1NzM5NTIwOX0.bepjGzYMGld7o_KB39Z3K7nYmt4plVKotvF37E8zawA",
|
||||
"token_type": "bearer"
|
||||
}
|
||||
[2025-08-10 14:20:09] Got access_token (hidden)
|
||||
-----
|
||||
-----
|
||||
[2025-08-10 14:20:09] GET /profiles/me
|
||||
[2025-08-10 14:20:09] URL: http://localhost:8080/profiles/v1/profiles/me
|
||||
[2025-08-10 14:20:09] HTTP: 404
|
||||
[2025-08-10 14:20:09] Body:
|
||||
{
|
||||
"detail": "Profile not found"
|
||||
}
|
||||
[2025-08-10 14:20:09] Profile not found — creating…
|
||||
-----
|
||||
[2025-08-10 14:20:09] CREATE /profiles
|
||||
[2025-08-10 14:20:09] URL: http://localhost:8080/profiles/v1/profiles
|
||||
[2025-08-10 14:20:09] HTTP: 201
|
||||
[2025-08-10 14:20:09] Body:
|
||||
{
|
||||
"gender": "other",
|
||||
"city": "Moscow",
|
||||
"languages": [
|
||||
"ru",
|
||||
"en"
|
||||
],
|
||||
"interests": [
|
||||
"music",
|
||||
"travel"
|
||||
],
|
||||
"id": "aa6d6046-b235-4727-a37d-9b74f36cce00",
|
||||
"user_id": "f4ffd570-5f43-4268-ae6c-cf3f8ec57793",
|
||||
"photo_url": null
|
||||
}
|
||||
-----
|
||||
[2025-08-10 14:20:09] GET /profiles/me (after create)
|
||||
[2025-08-10 14:20:09] URL: http://localhost:8080/profiles/v1/profiles/me
|
||||
[2025-08-10 14:20:09] HTTP: 200
|
||||
[2025-08-10 14:20:09] Body:
|
||||
{
|
||||
"gender": "other",
|
||||
"city": "Moscow",
|
||||
"languages": [
|
||||
"ru",
|
||||
"en"
|
||||
],
|
||||
"interests": [
|
||||
"music",
|
||||
"travel"
|
||||
],
|
||||
"id": "aa6d6046-b235-4727-a37d-9b74f36cce00",
|
||||
"user_id": "f4ffd570-5f43-4268-ae6c-cf3f8ec57793",
|
||||
"photo_url": null
|
||||
}
|
||||
[2025-08-10 14:20:09] DONE. Log saved to: logs/user_flow_20250810_142009.log
|
||||
1
match/src/app/api/routes/pairs.py
Normal file
1
match/src/app/api/routes/pairs.py
Normal file
@@ -0,0 +1 @@
|
||||
'"$@"'
|
||||
1
profiles/src/app/api/routes/profiles.py
Normal file
1
profiles/src/app/api/routes/profiles.py
Normal file
@@ -0,0 +1 @@
|
||||
'"$@"'
|
||||
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}"
|
||||
|
||||
@@ -7,6 +7,8 @@ WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt
|
||||
RUN mkdir -p /app/uploads
|
||||
|
||||
|
||||
COPY src ./src
|
||||
COPY alembic.ini ./
|
||||
|
||||
6
services/auth/docker-entrypoint.sh.bak.1754801031
Executable file
6
services/auth/docker-entrypoint.sh.bak.1754801031
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
# Run migrations (no-op if no revisions yet)
|
||||
alembic -c alembic.ini upgrade head || true
|
||||
# Start app
|
||||
exec uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
26
services/auth/src/app/api/routes/users_search.py
Normal file
26
services/auth/src/app/api/routes/users_search.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from typing import Optional, List
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from ...db.session import get_db
|
||||
from ...core.security import require_roles, UserClaims
|
||||
from ...schemas.user import UserRead
|
||||
from ...repositories.user_search_repository import UserSearchRepository
|
||||
|
||||
router = APIRouter(prefix="/v1/users", tags=["users"])
|
||||
|
||||
@router.get("/search", response_model=List[UserRead])
|
||||
def search_users(q: Optional[str] = None,
|
||||
role: Optional[str] = Query(None, pattern="^(ADMIN|CLIENT)$"),
|
||||
is_active: Optional[bool] = None,
|
||||
email_domain: Optional[str] = None,
|
||||
created_from: Optional[str] = None,
|
||||
created_to: Optional[str] = None,
|
||||
sort_by: Optional[str] = Query(None, pattern="^(full_name|email|created_at|role|is_active)$"),
|
||||
order: Optional[str] = Query("asc", pattern="^(asc|desc)$"),
|
||||
offset: int = 0, limit: int = Query(50, le=200),
|
||||
db: Session = Depends(get_db),
|
||||
_: UserClaims = Depends(require_roles("ADMIN"))):
|
||||
repo = UserSearchRepository(db)
|
||||
return repo.search(q=q, role=role, is_active=is_active, email_domain=email_domain,
|
||||
created_from=created_from, created_to=created_to,
|
||||
sort_by=sort_by, order=order, offset=offset, limit=limit)
|
||||
14
services/auth/src/app/main.py.bak.1754798399
Normal file
14
services/auth/src/app/main.py.bak.1754798399
Normal file
@@ -0,0 +1,14 @@
|
||||
from fastapi import FastAPI
|
||||
from .api.routes.ping import router as ping_router
|
||||
from .api.routes.auth import router as auth_router
|
||||
from .api.routes.users import router as users_router
|
||||
|
||||
app = FastAPI(title="AUTH Service")
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok", "service": "auth"}
|
||||
|
||||
app.include_router(ping_router, prefix="/v1")
|
||||
app.include_router(auth_router)
|
||||
app.include_router(users_router)
|
||||
38
services/auth/src/app/repositories/user_search_repository.py
Normal file
38
services/auth/src/app/repositories/user_search_repository.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_, func
|
||||
from ..models.user import User
|
||||
|
||||
class UserSearchRepository:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def search(self, q: Optional[str], role: Optional[str], is_active: Optional[bool],
|
||||
email_domain: Optional[str], created_from: Optional[str], created_to: Optional[str],
|
||||
sort_by: Optional[str], order: Optional[str], offset: int, limit: int) -> List[User]:
|
||||
qry = self.db.query(User)
|
||||
if q:
|
||||
like = f"%{q}%"
|
||||
qry = qry.filter(or_(User.email.ilike(like), User.full_name.ilike(like)))
|
||||
if role:
|
||||
qry = qry.filter(User.role == role)
|
||||
if is_active is not None:
|
||||
qry = qry.filter(User.is_active == is_active)
|
||||
if email_domain:
|
||||
qry = qry.filter(User.email.ilike(f"%@{email_domain}"))
|
||||
if created_from:
|
||||
qry = qry.filter(User.created_at >= created_from)
|
||||
if created_to:
|
||||
qry = qry.filter(User.created_at <= created_to)
|
||||
|
||||
sort_map = {
|
||||
"full_name": User.full_name,
|
||||
"email": User.email,
|
||||
"created_at": User.created_at,
|
||||
"role": User.role,
|
||||
"is_active": User.is_active
|
||||
}
|
||||
col = sort_map.get(sort_by or "", User.created_at)
|
||||
if order == "desc":
|
||||
col = col.desc()
|
||||
return qry.order_by(col).offset(offset).limit(min(limit, 200)).all()
|
||||
@@ -7,6 +7,8 @@ WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt
|
||||
RUN mkdir -p /app/uploads
|
||||
|
||||
|
||||
COPY src ./src
|
||||
COPY alembic.ini ./
|
||||
|
||||
6
services/chat/docker-entrypoint.sh.bak.1754801031
Executable file
6
services/chat/docker-entrypoint.sh.bak.1754801031
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
# Run migrations (no-op if no revisions yet)
|
||||
alembic -c alembic.ini upgrade head || true
|
||||
# Start app
|
||||
exec uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
router = APIRouter(prefix="/v1", tags=["chat"])
|
||||
|
||||
@@ -19,12 +20,11 @@ def create_room(payload: RoomCreate, db: Session = Depends(get_db), user: UserCl
|
||||
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: str, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
|
||||
room = ChatService(db).get_room(room_id)
|
||||
@router.get("/v1/rooms/{room_id}", response_model=RoomRead)
|
||||
def get_room(room_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(require_auth)):
|
||||
room = RoomService(db).get(room_id, user.sub)
|
||||
if not room:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
# NOTE: для простоты опускаем проверку участия (добавьте в проде)
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
return room
|
||||
|
||||
@router.post("/rooms/{room_id}/messages", response_model=MessageRead, status_code=201)
|
||||
@@ -36,11 +36,8 @@ def send_message(room_id: str, payload: MessageCreate, db: Session = Depends(get
|
||||
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: str, 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)
|
||||
if not room:
|
||||
@router.get("/v1/rooms/{room_id}/messages", response_model=list[MessageRead])
|
||||
def list_messages(room_id: UUID, offset: int = 0, limit: int = 100, db: Session = Depends(get_db), user: UserClaims = Depends(require_auth)):
|
||||
if not RoomService(db).exists(room_id, user.sub):
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
return svc.list_messages(room_id, offset=offset, limit=limit)
|
||||
return MessageService(db).list(room_id, user.sub, offset, limit)
|
||||
|
||||
@@ -2,6 +2,8 @@ FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN mkdir -p /app/uploads
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY main.py .
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt
|
||||
RUN mkdir -p /app/uploads
|
||||
RUN mkdir -p /app/uploads
|
||||
|
||||
|
||||
COPY src ./src
|
||||
COPY alembic.ini ./
|
||||
|
||||
6
services/match/docker-entrypoint.sh.bak.1754801031
Executable file
6
services/match/docker-entrypoint.sh.bak.1754801031
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
# Run migrations (no-op if no revisions yet)
|
||||
alembic -c alembic.ini upgrade head || true
|
||||
# Start app
|
||||
exec uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.session import get_db
|
||||
@@ -23,42 +23,33 @@ def list_pairs(for_user_id: str | None = None, status: str | None = None,
|
||||
_: 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: str, db: Session = Depends(get_db), _: UserClaims = Depends(get_current_user)):
|
||||
obj = PairService(db).get(pair_id)
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return obj
|
||||
@router.get("/v1/pairs/{pair_id}", response_model=PairRead)
|
||||
def get_pair(pair_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(require_auth)):
|
||||
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: str, payload: PairUpdate, db: Session = Depends(get_db),
|
||||
_: UserClaims = Depends(require_roles("ADMIN","MATCHMAKER"))):
|
||||
svc = PairService(db)
|
||||
obj = svc.get(pair_id)
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return svc.update(obj, **payload.model_dump(exclude_none=True))
|
||||
@router.patch("/v1/pairs/{pair_id}", response_model=PairRead)
|
||||
def update_pair(pair_id: UUID, payload: PairUpdate, db: Session = Depends(get_db), user: UserClaims = Depends(require_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: str, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
|
||||
svc = PairService(db)
|
||||
obj = svc.get(pair_id)
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
# Validate that current user participates
|
||||
if user.sub not in (str(obj.user_id_a), str(obj.user_id_b)):
|
||||
raise HTTPException(status_code=403, detail="Not allowed")
|
||||
return svc.set_status(obj, "accepted")
|
||||
@router.post("/v1/pairs/{pair_id}/accept", response_model=PairRead)
|
||||
def accept(pair_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(require_auth)):
|
||||
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: str, db: Session = Depends(get_db), user: UserClaims = Depends(get_current_user)):
|
||||
svc = PairService(db)
|
||||
obj = svc.get(pair_id)
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
if user.sub not in (str(obj.user_id_a), str(obj.user_id_b)):
|
||||
raise HTTPException(status_code=403, detail="Not allowed")
|
||||
return svc.set_status(obj, "rejected")
|
||||
@router.post("/v1/pairs/{pair_id}/reject", response_model=PairRead)
|
||||
def reject(pair_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(require_auth)):
|
||||
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: str, db: Session = Depends(get_db),
|
||||
|
||||
@@ -7,6 +7,8 @@ WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt
|
||||
RUN mkdir -p /app/uploads
|
||||
|
||||
|
||||
COPY src ./src
|
||||
COPY alembic.ini ./
|
||||
|
||||
6
services/payments/docker-entrypoint.sh.bak.1754801031
Executable file
6
services/payments/docker-entrypoint.sh.bak.1754801031
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
# Run migrations (no-op if no revisions yet)
|
||||
alembic -c alembic.ini upgrade head || true
|
||||
# Start app
|
||||
exec uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
@@ -7,6 +7,7 @@ WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt
|
||||
RUN mkdir -p /app/uploads
|
||||
|
||||
COPY src ./src
|
||||
COPY alembic.ini ./
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'add_profile_photo_and_likes'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
# photo_url
|
||||
with op.batch_alter_table('profiles', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('photo_url', sa.String(), nullable=True))
|
||||
|
||||
# likes
|
||||
op.create_table(
|
||||
'profile_likes',
|
||||
sa.Column('liker_user_id', sa.String(), primary_key=True),
|
||||
sa.Column('target_user_id', sa.String(), primary_key=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
)
|
||||
op.create_unique_constraint('uq_profile_like', 'profile_likes', ['liker_user_id', 'target_user_id'])
|
||||
|
||||
def downgrade():
|
||||
op.drop_constraint('uq_profile_like', 'profile_likes', type_='unique')
|
||||
op.drop_table('profile_likes')
|
||||
with op.batch_alter_table('profiles', schema=None) as batch_op:
|
||||
batch_op.drop_column('photo_url')
|
||||
6
services/profiles/docker-entrypoint.sh.bak.1754801031
Executable file
6
services/profiles/docker-entrypoint.sh.bak.1754801031
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
# Run migrations (no-op if no revisions yet)
|
||||
alembic -c alembic.ini upgrade head || true
|
||||
# Start app
|
||||
exec uvicorn app.main:app --host 0.0.0.0 --port 8000 --log-level debug
|
||||
@@ -9,3 +9,4 @@ python-dotenv
|
||||
httpx>=0.27
|
||||
pytest
|
||||
PyJWT>=2.8
|
||||
python-multipart==0.0.9
|
||||
|
||||
@@ -1,31 +1,143 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
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
|
||||
import os
|
||||
from fastapi import status
|
||||
|
||||
from app.db.deps import get_db
|
||||
from app.core.security import get_current_user, JwtUser
|
||||
from app.schemas.profile import ProfileCreate, ProfileOut
|
||||
from app.repositories.profile_repository import ProfileRepository
|
||||
from app.services.profile_service import ProfileService
|
||||
router = APIRouter(prefix="/v1/profiles", tags=["profiles"])
|
||||
|
||||
# отключаем авто-редирект /path -> /path/
|
||||
router = APIRouter(prefix="/v1/profiles", tags=["profiles"], redirect_slashes=False)
|
||||
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_my_profile(current: JwtUser = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)):
|
||||
svc = ProfileService(ProfileRepository(db))
|
||||
p = svc.get_by_user(current.sub)
|
||||
if not p:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found")
|
||||
return p
|
||||
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=status.HTTP_201_CREATED)
|
||||
def create_my_profile(payload: ProfileCreate,
|
||||
current: JwtUser = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)):
|
||||
svc = ProfileService(ProfileRepository(db))
|
||||
existing = svc.get_by_user(current.sub)
|
||||
if existing:
|
||||
# если хотите строго — верните 409; оставлю 200/201 для удобства e2e
|
||||
return existing
|
||||
return svc.create(current.sub, payload)
|
||||
@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)):
|
||||
# ADMIN видит всех; CLIENT — тоже ok для MVP
|
||||
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("/v1/profiles/{profile_id}", response_model=ProfileOut)
|
||||
def get_profile(profile_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(require_auth)):
|
||||
prof = ProfileService(db).get(profile_id)
|
||||
if not prof:
|
||||
raise HTTPException(status_code=404, detail="Profile not found")
|
||||
return prof
|
||||
|
||||
@router.get("/v1/profiles/by-user/{user_id}", response_model=ProfileOut)
|
||||
def get_by_user(user_id: UUID, db: Session = Depends(get_db), user: UserClaims = Depends(require_auth)):
|
||||
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
|
||||
|
||||
# === Photo upload ===
|
||||
@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())
|
||||
|
||||
# URL через gateway: /profiles/static/avatars/<filename>
|
||||
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)
|
||||
|
||||
# === Likes ===
|
||||
@router.get("/../likes", response_model=LikesList, include_in_schema=False)
|
||||
def _compat_likes_redirect():
|
||||
# Заглушка для генераторов — реальный path ниже (/v1/likes)
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.deps import get_db
|
||||
from app.core.security import get_current_user, JwtUser
|
||||
from app.schemas.profile import ProfileCreate, ProfileOut
|
||||
from app.repositories.profile_repository import ProfileRepository
|
||||
from app.services.profile_service import ProfileService
|
||||
|
||||
# отключаем авто-редирект /path -> /path/
|
||||
router = APIRouter(prefix="/v1/profiles", tags=["profiles"], redirect_slashes=False)
|
||||
|
||||
@router.get("/me", response_model=ProfileOut)
|
||||
def get_my_profile(current: JwtUser = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)):
|
||||
svc = ProfileService(ProfileRepository(db))
|
||||
p = svc.get_by_user(current.sub)
|
||||
if not p:
|
||||
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_my_profile(payload: ProfileCreate,
|
||||
current: JwtUser = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)):
|
||||
svc = ProfileService(ProfileRepository(db))
|
||||
existing = svc.get_by_user(current.sub)
|
||||
if existing:
|
||||
# если хотите строго — верните 409; оставлю 200/201 для удобства e2e
|
||||
return existing
|
||||
return svc.create(current.sub, payload)
|
||||
147
services/profiles/src/app/api/routes/profiles.py.bak.1754798570
Normal file
147
services/profiles/src/app/api/routes/profiles.py.bak.1754798570
Normal file
@@ -0,0 +1,147 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query
|
||||
from sqlalchemy.orm import Session
|
||||
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
|
||||
import os
|
||||
from fastapi import status
|
||||
|
||||
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)):
|
||||
# ADMIN видит всех; CLIENT — тоже ok для MVP
|
||||
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: str, 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="Not found")
|
||||
if user.role != "ADMIN" and prof.user_id != user.sub:
|
||||
# при необходимости можно сделать публичным — тут ограничение на владение/admin
|
||||
pass
|
||||
return prof
|
||||
|
||||
@router.get("/by-user/{user_id}", response_model=ProfileOut)
|
||||
def get_by_user(user_id: str, 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="Not found")
|
||||
if user.role != "ADMIN" and user_id != user.sub:
|
||||
pass
|
||||
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
|
||||
|
||||
# === Photo upload ===
|
||||
@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())
|
||||
|
||||
# URL через gateway: /profiles/static/avatars/<filename>
|
||||
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)
|
||||
|
||||
# === Likes ===
|
||||
@router.get("/../likes", response_model=LikesList, include_in_schema=False)
|
||||
def _compat_likes_redirect():
|
||||
# Заглушка для генераторов — реальный path ниже (/v1/likes)
|
||||
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)
|
||||
147
services/profiles/src/app/api/routes/profiles.py.bak.1754798889
Normal file
147
services/profiles/src/app/api/routes/profiles.py.bak.1754798889
Normal file
@@ -0,0 +1,147 @@
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query
|
||||
from sqlalchemy.orm import Session
|
||||
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
|
||||
import os
|
||||
from fastapi import status
|
||||
|
||||
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)):
|
||||
# ADMIN видит всех; CLIENT — тоже ok для MVP
|
||||
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: str, 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="Not found")
|
||||
if user.role != "ADMIN" and prof.user_id != user.sub:
|
||||
# при необходимости можно сделать публичным — тут ограничение на владение/admin
|
||||
pass
|
||||
return prof
|
||||
|
||||
@router.get("/by-user/{user_id}", response_model=ProfileOut)
|
||||
def get_by_user(user_id: str, 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="Not found")
|
||||
if user.role != "ADMIN" and user_id != user.sub:
|
||||
pass
|
||||
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
|
||||
|
||||
# === Photo upload ===
|
||||
@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())
|
||||
|
||||
# URL через gateway: /profiles/static/avatars/<filename>
|
||||
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)
|
||||
|
||||
# === Likes ===
|
||||
@router.get("/../likes", response_model=LikesList, include_in_schema=False)
|
||||
def _compat_likes_redirect():
|
||||
# Заглушка для генераторов — реальный path ниже (/v1/likes)
|
||||
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)
|
||||
@@ -57,3 +57,64 @@ def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(reusabl
|
||||
if credentials.scheme.lower() != "bearer":
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid auth scheme")
|
||||
return decode_token(credentials.credentials)
|
||||
|
||||
# --- added by patch: role-based dependency ---
|
||||
from fastapi import Depends, HTTPException, status # noqa: E402
|
||||
|
||||
def require_roles(*roles: str):
|
||||
"""
|
||||
FastAPI dependency: ensure current user has one of the roles.
|
||||
Usage: Depends(require_roles("ADMIN", "MATCHMAKER"))
|
||||
"""
|
||||
def _dep(user: "UserClaims" = Depends(get_current_user)):
|
||||
if not getattr(user, "role", None) in roles:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")
|
||||
return user
|
||||
return _dep
|
||||
# --- end patch ---
|
||||
|
||||
# --- added by patch: UserClaims + normalization ---
|
||||
from typing import Optional, Any # noqa: E402
|
||||
from pydantic import BaseModel # noqa: E402
|
||||
|
||||
class UserClaims(BaseModel):
|
||||
sub: str
|
||||
role: str
|
||||
email: Optional[str] = None
|
||||
|
||||
def _as_user_claims(u: Any) -> "UserClaims":
|
||||
"""Приводит произвольный объект пользователя к UserClaims."""
|
||||
if isinstance(u, UserClaims):
|
||||
return u
|
||||
if isinstance(u, dict):
|
||||
return UserClaims(**u)
|
||||
if hasattr(u, "model_dump"): # pydantic v2
|
||||
return UserClaims(**u.model_dump())
|
||||
# на крайний случай — достанем атрибуты
|
||||
return UserClaims(
|
||||
sub=str(getattr(u, "sub", "")),
|
||||
role=str(getattr(u, "role", "")),
|
||||
email=getattr(u, "email", None),
|
||||
)
|
||||
|
||||
# если есть require_roles — заставим его использовать нормализацию
|
||||
try:
|
||||
# find the already-patched require_roles and wrap its inner dep
|
||||
import inspect
|
||||
if 'require_roles' in globals():
|
||||
_orig_require_roles = require_roles
|
||||
def require_roles(*roles: str): # type: ignore[override]
|
||||
dep = _orig_require_roles(*roles)
|
||||
def _wrapped(user = Depends(get_current_user)): # noqa: F821
|
||||
u = _as_user_claims(user)
|
||||
if u.role not in roles:
|
||||
from fastapi import HTTPException, status
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")
|
||||
return u
|
||||
# Вернём совместимый Depends
|
||||
from fastapi import Depends
|
||||
return _wrapped
|
||||
except Exception:
|
||||
# тихо игнорируем — значит require_roles ещё не определён, всё ок
|
||||
pass
|
||||
# --- end patch ---
|
||||
|
||||
59
services/profiles/src/app/core/security.py.bak.1754799021
Normal file
59
services/profiles/src/app/core/security.py.bak.1754799021
Normal file
@@ -0,0 +1,59 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from pydantic import BaseModel
|
||||
|
||||
reusable_bearer = HTTPBearer(auto_error=True)
|
||||
|
||||
JWT_SECRET = os.getenv("JWT_SECRET", "dev-secret")
|
||||
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256")
|
||||
|
||||
# Возможность включить строгую проверку audience/issuer в будущем
|
||||
JWT_VERIFY_AUD = os.getenv("JWT_VERIFY_AUD", "0") == "1"
|
||||
JWT_AUDIENCE: Optional[str] = os.getenv("JWT_AUDIENCE") or None
|
||||
JWT_VERIFY_ISS = os.getenv("JWT_VERIFY_ISS", "0") == "1"
|
||||
JWT_ISSUER: Optional[str] = os.getenv("JWT_ISSUER") or None
|
||||
|
||||
# Допустимая рассинхронизация часов (сек)
|
||||
JWT_LEEWAY = int(os.getenv("JWT_LEEWAY", "30"))
|
||||
|
||||
class JwtUser(BaseModel):
|
||||
sub: str
|
||||
email: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
|
||||
def decode_token(token: str) -> JwtUser:
|
||||
options = {
|
||||
"verify_signature": True,
|
||||
"verify_exp": True,
|
||||
"verify_aud": JWT_VERIFY_AUD,
|
||||
"verify_iss": JWT_VERIFY_ISS,
|
||||
}
|
||||
kwargs = {"algorithms": [JWT_ALGORITHM], "options": options, "leeway": JWT_LEEWAY}
|
||||
if JWT_VERIFY_AUD and JWT_AUDIENCE:
|
||||
kwargs["audience"] = JWT_AUDIENCE
|
||||
if JWT_VERIFY_ISS and JWT_ISSUER:
|
||||
kwargs["issuer"] = JWT_ISSUER
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, JWT_SECRET, **kwargs)
|
||||
sub = str(payload.get("sub") or "")
|
||||
if not sub:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token: no sub")
|
||||
return JwtUser(sub=sub, email=payload.get("email"), role=payload.get("role"))
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token expired")
|
||||
except jwt.InvalidAudienceError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid audience")
|
||||
except jwt.InvalidIssuerError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid issuer")
|
||||
except jwt.InvalidTokenError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
|
||||
|
||||
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(reusable_bearer)) -> JwtUser:
|
||||
if credentials.scheme.lower() != "bearer":
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid auth scheme")
|
||||
return decode_token(credentials.credentials)
|
||||
74
services/profiles/src/app/core/security.py.bak.1754799216
Normal file
74
services/profiles/src/app/core/security.py.bak.1754799216
Normal file
@@ -0,0 +1,74 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from pydantic import BaseModel
|
||||
|
||||
reusable_bearer = HTTPBearer(auto_error=True)
|
||||
|
||||
JWT_SECRET = os.getenv("JWT_SECRET", "dev-secret")
|
||||
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256")
|
||||
|
||||
# Возможность включить строгую проверку audience/issuer в будущем
|
||||
JWT_VERIFY_AUD = os.getenv("JWT_VERIFY_AUD", "0") == "1"
|
||||
JWT_AUDIENCE: Optional[str] = os.getenv("JWT_AUDIENCE") or None
|
||||
JWT_VERIFY_ISS = os.getenv("JWT_VERIFY_ISS", "0") == "1"
|
||||
JWT_ISSUER: Optional[str] = os.getenv("JWT_ISSUER") or None
|
||||
|
||||
# Допустимая рассинхронизация часов (сек)
|
||||
JWT_LEEWAY = int(os.getenv("JWT_LEEWAY", "30"))
|
||||
|
||||
class JwtUser(BaseModel):
|
||||
sub: str
|
||||
email: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
|
||||
def decode_token(token: str) -> JwtUser:
|
||||
options = {
|
||||
"verify_signature": True,
|
||||
"verify_exp": True,
|
||||
"verify_aud": JWT_VERIFY_AUD,
|
||||
"verify_iss": JWT_VERIFY_ISS,
|
||||
}
|
||||
kwargs = {"algorithms": [JWT_ALGORITHM], "options": options, "leeway": JWT_LEEWAY}
|
||||
if JWT_VERIFY_AUD and JWT_AUDIENCE:
|
||||
kwargs["audience"] = JWT_AUDIENCE
|
||||
if JWT_VERIFY_ISS and JWT_ISSUER:
|
||||
kwargs["issuer"] = JWT_ISSUER
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, JWT_SECRET, **kwargs)
|
||||
sub = str(payload.get("sub") or "")
|
||||
if not sub:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token: no sub")
|
||||
return JwtUser(sub=sub, email=payload.get("email"), role=payload.get("role"))
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token expired")
|
||||
except jwt.InvalidAudienceError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid audience")
|
||||
except jwt.InvalidIssuerError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid issuer")
|
||||
except jwt.InvalidTokenError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
|
||||
|
||||
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(reusable_bearer)) -> JwtUser:
|
||||
if credentials.scheme.lower() != "bearer":
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid auth scheme")
|
||||
return decode_token(credentials.credentials)
|
||||
|
||||
# --- added by patch: role-based dependency ---
|
||||
from fastapi import Depends, HTTPException, status # noqa: E402
|
||||
|
||||
def require_roles(*roles: str):
|
||||
"""
|
||||
FastAPI dependency: ensure current user has one of the roles.
|
||||
Usage: Depends(require_roles("ADMIN", "MATCHMAKER"))
|
||||
"""
|
||||
def _dep(user: "UserClaims" = Depends(get_current_user)):
|
||||
if not getattr(user, "role", None) in roles:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")
|
||||
return user
|
||||
return _dep
|
||||
# --- end patch ---
|
||||
@@ -1,12 +1,17 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.staticfiles import StaticFiles
|
||||
from .api.routes.ping import router as ping_router
|
||||
from .api.routes.profiles import router as profiles_router
|
||||
from .api.routes.profiles import router as profiles_router, likes_router as profiles_likes_router
|
||||
|
||||
app = FastAPI(title="PROFILES Service")
|
||||
|
||||
|
||||
app.mount("/static", StaticFiles(directory="/app/uploads"), name="static")
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok", "service": "profiles"}
|
||||
|
||||
app.include_router(ping_router, prefix="/v1")
|
||||
app.include_router(profiles_router)
|
||||
|
||||
app.include_router(profiles_likes_router)
|
||||
|
||||
12
services/profiles/src/app/models/like.py
Normal file
12
services/profiles/src/app/models/like.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, String, DateTime, UniqueConstraint
|
||||
from .base import Base
|
||||
|
||||
class ProfileLike(Base):
|
||||
__tablename__ = "profile_likes"
|
||||
liker_user_id = Column(String, primary_key=True) # UUID (as str)
|
||||
target_user_id = Column(String, primary_key=True) # UUID (as str)
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
__table_args__ = (
|
||||
UniqueConstraint('liker_user_id', 'target_user_id', name='uq_profile_like'),
|
||||
)
|
||||
37
services/profiles/src/app/repositories/likes_repository.py
Normal file
37
services/profiles/src/app/repositories/likes_repository.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from typing import List
|
||||
from sqlalchemy.orm import Session
|
||||
from ..models.like import ProfileLike
|
||||
|
||||
class LikesRepository:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def list_my_likes(self, user_id: str) -> List[str]:
|
||||
rows = self.db.query(ProfileLike).filter(ProfileLike.liker_user_id == user_id).all()
|
||||
return [r.target_user_id for r in rows]
|
||||
|
||||
def put_like(self, liker: str, target: str) -> None:
|
||||
exists = self.db.query(ProfileLike).filter(
|
||||
ProfileLike.liker_user_id == liker,
|
||||
ProfileLike.target_user_id == target
|
||||
).first()
|
||||
if exists:
|
||||
return
|
||||
self.db.add(ProfileLike(liker_user_id=liker, target_user_id=target))
|
||||
self.db.commit()
|
||||
|
||||
def delete_like(self, liker: str, target: str) -> None:
|
||||
self.db.query(ProfileLike).filter(
|
||||
ProfileLike.liker_user_id == liker,
|
||||
ProfileLike.target_user_id == target
|
||||
).delete()
|
||||
self.db.commit()
|
||||
|
||||
def mutual_likes(self, user_id: str) -> List[str]:
|
||||
# users that user_id likes AND who like user_id back
|
||||
sub = self.db.query(ProfileLike.target_user_id).filter(ProfileLike.liker_user_id == user_id).subquery()
|
||||
rows = self.db.query(ProfileLike.liker_user_id).filter(
|
||||
ProfileLike.target_user_id == user_id,
|
||||
ProfileLike.liker_user_id.in_(sub)
|
||||
).all()
|
||||
return [r[0] for r in rows]
|
||||
@@ -1,26 +1,81 @@
|
||||
from typing import Optional
|
||||
# services/profiles/src/app/repositories/profile_repository.py
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.models.profile import Profile
|
||||
from app.schemas.profile import ProfileCreate
|
||||
|
||||
|
||||
class ProfileRepository:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_by_user(self, user_id: UUID) -> Optional[Profile]:
|
||||
return self.db.execute(select(Profile).where(Profile.user_id == user_id)).scalar_one_or_none()
|
||||
|
||||
def create(self, user_id: UUID, data: ProfileCreate) -> Profile:
|
||||
p = Profile(
|
||||
user_id=user_id,
|
||||
gender=data.gender,
|
||||
city=data.city,
|
||||
languages=list(data.languages or []),
|
||||
interests=list(data.interests or []),
|
||||
def get_by_user_id(self, user_id: UUID) -> Optional[Profile]:
|
||||
return (
|
||||
self.db.query(Profile)
|
||||
.filter(Profile.user_id == user_id)
|
||||
.first()
|
||||
)
|
||||
self.db.add(p)
|
||||
|
||||
# оставляем старое имя для обратной совместимости
|
||||
def get_by_user(self, user_id: UUID) -> Optional[Profile]:
|
||||
return self.get_by_user_id(user_id)
|
||||
|
||||
def get_by_id(self, profile_id: UUID) -> Optional[Profile]:
|
||||
return self.db.query(Profile).filter(Profile.id == profile_id).first()
|
||||
|
||||
def create(
|
||||
self,
|
||||
*,
|
||||
user_id: UUID,
|
||||
gender: str,
|
||||
city: str,
|
||||
languages: List[str],
|
||||
interests: List[str],
|
||||
photo_url: Optional[str] = None,
|
||||
) -> Profile:
|
||||
# не передаём photo_url в конструктор — модели может не быть этого поля
|
||||
obj = Profile(
|
||||
user_id=user_id,
|
||||
gender=gender,
|
||||
city=city,
|
||||
languages=languages,
|
||||
interests=interests,
|
||||
)
|
||||
# выставим только если поле реально существует
|
||||
if photo_url is not None and hasattr(obj, "photo_url"):
|
||||
setattr(obj, "photo_url", photo_url)
|
||||
|
||||
self.db.add(obj)
|
||||
self.db.commit()
|
||||
self.db.refresh(p)
|
||||
return p
|
||||
self.db.refresh(obj)
|
||||
return obj
|
||||
|
||||
def update_me(
|
||||
self,
|
||||
*,
|
||||
profile: Profile,
|
||||
gender: Optional[str] = None,
|
||||
city: Optional[str] = None,
|
||||
languages: Optional[List[str]] = None,
|
||||
interests: Optional[List[str]] = None,
|
||||
photo_url: Optional[Optional[str]] = None, # None — «не менять», явный null — «стереть»
|
||||
) -> Profile:
|
||||
if gender is not None:
|
||||
profile.gender = gender
|
||||
if city is not None:
|
||||
profile.city = city
|
||||
if languages is not None:
|
||||
profile.languages = languages
|
||||
if interests is not None:
|
||||
profile.interests = interests
|
||||
if photo_url is not None and hasattr(profile, "photo_url"):
|
||||
# сюда придёт либо строка (установить), либо явный None (стереть)
|
||||
setattr(profile, "photo_url", photo_url)
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(profile)
|
||||
return profile
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from app.models.profile import Profile
|
||||
from app.schemas.profile import ProfileCreate
|
||||
|
||||
class ProfileRepository:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_by_user(self, user_id: UUID) -> Optional[Profile]:
|
||||
return self.db.execute(select(Profile).where(Profile.user_id == user_id)).scalar_one_or_none()
|
||||
|
||||
def create(self, user_id: UUID, data: ProfileCreate) -> Profile:
|
||||
p = Profile(
|
||||
user_id=user_id,
|
||||
gender=data.gender,
|
||||
city=data.city,
|
||||
languages=list(data.languages or []),
|
||||
interests=list(data.interests or []),
|
||||
)
|
||||
self.db.add(p)
|
||||
self.db.commit()
|
||||
self.db.refresh(p)
|
||||
return p
|
||||
@@ -0,0 +1,55 @@
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_, func, text
|
||||
from ..models.profile import Profile
|
||||
|
||||
class ProfileSearchRepository:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def list_profiles(self,
|
||||
q: Optional[str],
|
||||
gender: Optional[str],
|
||||
city: Optional[str],
|
||||
languages: Optional[List[str]],
|
||||
interests: Optional[List[str]],
|
||||
has_photo: Optional[bool],
|
||||
sort_by: Optional[str],
|
||||
order: Optional[str],
|
||||
offset: int, limit: int) -> List[Profile]:
|
||||
query = self.db.query(Profile)
|
||||
|
||||
if q:
|
||||
ilike = f"%{q}%"
|
||||
query = query.filter(
|
||||
or_(Profile.city.ilike(ilike),
|
||||
func.cast(Profile.languages, text('TEXT')).ilike(ilike),
|
||||
func.cast(Profile.interests, text('TEXT')).ilike(ilike))
|
||||
)
|
||||
if gender:
|
||||
query = query.filter(Profile.gender == gender)
|
||||
if city:
|
||||
query = query.filter(Profile.city.ilike(city) | (Profile.city == city))
|
||||
if languages:
|
||||
# пересечение хотя бы по одному
|
||||
for l in languages:
|
||||
query = query.filter(func.cast(Profile.languages, text('TEXT')).ilike(f'%"{l}"%'))
|
||||
if interests:
|
||||
for i in interests:
|
||||
query = query.filter(func.cast(Profile.interests, text('TEXT')).ilike(f'%"{i}"%'))
|
||||
if has_photo is True:
|
||||
query = query.filter(Profile.photo_url.isnot(None))
|
||||
if has_photo is False:
|
||||
query = query.filter(Profile.photo_url.is_(None))
|
||||
|
||||
sort_map = {
|
||||
"created_at": Profile.created_at if hasattr(Profile, "created_at") else None,
|
||||
"updated_at": Profile.updated_at if hasattr(Profile, "updated_at") else None,
|
||||
"city": Profile.city,
|
||||
"gender": Profile.gender,
|
||||
}
|
||||
col = sort_map.get(sort_by or "", Profile.created_at if hasattr(Profile, "created_at") else Profile.id)
|
||||
if order == "desc":
|
||||
col = col.desc()
|
||||
query = query.order_by(col)
|
||||
return query.offset(offset).limit(min(limit, 200)).all()
|
||||
@@ -1,32 +1,77 @@
|
||||
# from pydantic import BaseModel, Field, HttpUrl
|
||||
# from typing import Optional, List, Literal
|
||||
# from uuid import UUID
|
||||
|
||||
# class ProfileCreate(BaseModel):
|
||||
# gender: Literal["male", "female", "other"]
|
||||
# city: str
|
||||
# languages: List[str] = []
|
||||
# interests: List[str] = []
|
||||
|
||||
# class ProfileUpdate(BaseModel):
|
||||
# gender: Optional[Literal["male", "female", "other"]] = None
|
||||
# city: Optional[str] = None
|
||||
# languages: Optional[List[str]] = None
|
||||
# interests: Optional[List[str]] = None
|
||||
# photo_url: Optional[Optional[str]] = Field(default=None)
|
||||
|
||||
# class ProfileOut(BaseModel):
|
||||
# id: UUID
|
||||
# user_id: UUID
|
||||
# gender: Literal["male", "female", "other"]
|
||||
# city: str
|
||||
# languages: List[str] = []
|
||||
# interests: List[str] = []
|
||||
# photo_url: Optional[str] = None
|
||||
|
||||
# from pydantic import ConfigDict
|
||||
# model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
# class LikesList(BaseModel):
|
||||
# items: List[str]
|
||||
|
||||
|
||||
# services/profiles/src/app/schemas/profile.py
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
|
||||
try:
|
||||
# Pydantic v2
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
_V2 = True
|
||||
except Exception:
|
||||
# Pydantic v1 fallback
|
||||
from pydantic import BaseModel, Field
|
||||
ConfigDict = None
|
||||
_V2 = False
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
# Базовые поля профиля
|
||||
class ProfileBase(BaseModel):
|
||||
gender: str
|
||||
city: str
|
||||
languages: List[str] = Field(default_factory=list)
|
||||
interests: List[str] = Field(default_factory=list)
|
||||
languages: List[str]
|
||||
interests: List[str]
|
||||
|
||||
|
||||
# Для POST /profiles/v1/profiles
|
||||
class ProfileCreate(ProfileBase):
|
||||
pass
|
||||
|
||||
|
||||
# Для PATCH /profiles/v1/profiles/me (все поля опциональные)
|
||||
class ProfileUpdate(BaseModel):
|
||||
gender: Optional[str] = None
|
||||
city: Optional[str] = None
|
||||
languages: Optional[List[str]] = None
|
||||
interests: Optional[List[str]] = None
|
||||
photo_url: Optional[str] = None # nullable
|
||||
|
||||
|
||||
# Для ответов
|
||||
class ProfileOut(ProfileBase):
|
||||
id: UUID
|
||||
user_id: UUID
|
||||
photo_url: Optional[str] = None
|
||||
|
||||
if _V2:
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
else:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
# Важно: для возврата ORM-объектов
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# Для лайков
|
||||
class LikesList(BaseModel):
|
||||
items: List[UUID]
|
||||
|
||||
33
services/profiles/src/app/schemas/profile.py.bak.1754798354
Normal file
33
services/profiles/src/app/schemas/profile.py.bak.1754798354
Normal file
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
|
||||
try:
|
||||
# Pydantic v2
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
_V2 = True
|
||||
except Exception:
|
||||
# Pydantic v1 fallback
|
||||
from pydantic import BaseModel, Field
|
||||
ConfigDict = None
|
||||
_V2 = False
|
||||
|
||||
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: UUID
|
||||
user_id: UUID
|
||||
|
||||
if _V2:
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
else:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
27
services/profiles/src/app/schemas/profile.py.bak.1754800672
Normal file
27
services/profiles/src/app/schemas/profile.py.bak.1754800672
Normal file
@@ -0,0 +1,27 @@
|
||||
from pydantic import BaseModel, Field, HttpUrl
|
||||
from typing import Optional, List, Literal
|
||||
|
||||
class ProfileCreate(BaseModel):
|
||||
gender: Literal["male", "female", "other"]
|
||||
city: str
|
||||
languages: List[str] = []
|
||||
interests: List[str] = []
|
||||
|
||||
class ProfileUpdate(BaseModel):
|
||||
gender: Optional[Literal["male", "female", "other"]] = None
|
||||
city: Optional[str] = None
|
||||
languages: Optional[List[str]] = None
|
||||
interests: Optional[List[str]] = None
|
||||
photo_url: Optional[Optional[str]] = Field(default=None)
|
||||
|
||||
class ProfileOut(BaseModel):
|
||||
id: str
|
||||
user_id: str
|
||||
gender: Literal["male", "female", "other"]
|
||||
city: str
|
||||
languages: List[str] = []
|
||||
interests: List[str] = []
|
||||
photo_url: Optional[str] = None
|
||||
|
||||
class LikesList(BaseModel):
|
||||
items: List[str]
|
||||
21
services/profiles/src/app/services/likes_service.py
Normal file
21
services/profiles/src/app/services/likes_service.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from typing import List
|
||||
from sqlalchemy.orm import Session
|
||||
from ..repositories.likes_repository import LikesRepository
|
||||
|
||||
class LikesService:
|
||||
def __init__(self, db: Session):
|
||||
self.repo = LikesRepository(db)
|
||||
|
||||
def list_my_likes(self, user_id: str) -> List[str]:
|
||||
return self.repo.list_my_likes(user_id)
|
||||
|
||||
def put_like(self, liker: str, target: str) -> None:
|
||||
if liker == target:
|
||||
return
|
||||
self.repo.put_like(liker, target)
|
||||
|
||||
def delete_like(self, liker: str, target: str) -> None:
|
||||
self.repo.delete_like(liker, target)
|
||||
|
||||
def mutual(self, user_id: str) -> List[str]:
|
||||
return self.repo.mutual_likes(user_id)
|
||||
11
services/profiles/src/app/services/profile_search_service.py
Normal file
11
services/profiles/src/app/services/profile_search_service.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from ..models.profile import Profile
|
||||
from ..repositories.profile_search_repository import ProfileSearchRepository
|
||||
|
||||
class ProfileSearchService:
|
||||
def __init__(self, db: Session):
|
||||
self.repo = ProfileSearchRepository(db)
|
||||
|
||||
def list_profiles(self, **kwargs) -> List[Profile]:
|
||||
return self.repo.list_profiles(**kwargs)
|
||||
@@ -1,13 +1,23 @@
|
||||
from uuid import UUID
|
||||
from app.schemas.profile import ProfileCreate
|
||||
from app.repositories.profile_repository import ProfileRepository
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from ..models.profile import Profile
|
||||
from ..repositories.profile_repository import ProfileRepository
|
||||
|
||||
class ProfileService:
|
||||
def __init__(self, repo: ProfileRepository):
|
||||
self.repo = repo
|
||||
def __init__(self, db: Session):
|
||||
self.repo = ProfileRepository(db)
|
||||
|
||||
def get_by_user(self, user_id: UUID):
|
||||
return self.repo.get_by_user(user_id)
|
||||
def get(self, profile_id: str) -> Optional[Profile]:
|
||||
return self.repo.get(profile_id)
|
||||
|
||||
def create(self, user_id: UUID, data: ProfileCreate):
|
||||
return self.repo.create(user_id, data)
|
||||
def get_by_user_id(self, user_id: str) -> Optional[Profile]:
|
||||
return self.repo.get_by_user_id(user_id)
|
||||
|
||||
def create(self, user_id: str, **data) -> Profile:
|
||||
return self.repo.create(user_id=user_id, **data)
|
||||
|
||||
def update(self, prof: Profile, **data) -> Profile:
|
||||
return self.repo.update(prof, **data)
|
||||
|
||||
def delete(self, prof: Profile) -> None:
|
||||
return self.repo.delete(prof)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from uuid import UUID
|
||||
from app.schemas.profile import ProfileCreate
|
||||
from app.repositories.profile_repository import ProfileRepository
|
||||
|
||||
class ProfileService:
|
||||
def __init__(self, repo: ProfileRepository):
|
||||
self.repo = repo
|
||||
|
||||
def get_by_user(self, user_id: UUID):
|
||||
return self.repo.get_by_user(user_id)
|
||||
|
||||
def create(self, user_id: UUID, data: ProfileCreate):
|
||||
return self.repo.create(user_id, data)
|
||||
Reference in New Issue
Block a user