This commit is contained in:
0
src/apps/esia/__init__.py
Normal file
0
src/apps/esia/__init__.py
Normal file
10
src/apps/esia/scopes.py
Normal file
10
src/apps/esia/scopes.py
Normal file
@ -0,0 +1,10 @@
|
||||
SCOPES = [
|
||||
'openid',
|
||||
'fullname',
|
||||
# 'email',
|
||||
# 'birthdate',
|
||||
# 'gender',
|
||||
# 'snils',
|
||||
# 'id_doc',
|
||||
# 'mobile',
|
||||
]
|
||||
82
src/apps/esia/sign.py
Normal file
82
src/apps/esia/sign.py
Normal file
@ -0,0 +1,82 @@
|
||||
import base64
|
||||
import secrets
|
||||
import subprocess # noqa: S404
|
||||
import tempfile
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from apps.esia.scopes import SCOPES
|
||||
from core.config import settings
|
||||
|
||||
ACCESS_TYPE = 'online'
|
||||
RESPONSE_CODE = 'code'
|
||||
|
||||
|
||||
def csp_sign(data: str):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
tmp_file_name = secrets.token_hex(8)
|
||||
source_path = Path(tmp_dir) / f'{tmp_file_name}.txt'
|
||||
destination_path = source_path.with_suffix('.txt.sgn')
|
||||
|
||||
with open(source_path, 'w', encoding='utf-8') as f:
|
||||
f.write(data)
|
||||
|
||||
print(data)
|
||||
|
||||
cmd = [
|
||||
'cryptcp',
|
||||
'-signf',
|
||||
'-norev',
|
||||
'-nochain',
|
||||
'-der',
|
||||
'-strict',
|
||||
'-cert',
|
||||
'-detached',
|
||||
'-thumbprint',
|
||||
settings.ESIA_CONTAINER_THUMBPRINT,
|
||||
'-pin',
|
||||
settings.ESIA_CONTAINER_PASSWORD,
|
||||
'-dir',
|
||||
tmp_dir,
|
||||
str(source_path),
|
||||
]
|
||||
|
||||
subprocess.run( # noqa: S603
|
||||
cmd, input=b'y\n', capture_output=True, check=True, text=False
|
||||
)
|
||||
signed_message = destination_path.read_bytes()
|
||||
|
||||
return signed_message
|
||||
|
||||
|
||||
def sign_params(params: dict[str, Any]):
|
||||
plaintext = (
|
||||
params.get('scope', '')
|
||||
+ params.get('timestamp', '')
|
||||
+ params.get('client_id', '')
|
||||
+ params.get('state', '')
|
||||
)
|
||||
|
||||
client_secret = csp_sign(plaintext)
|
||||
return base64.urlsafe_b64encode(client_secret).decode('utf-8')
|
||||
|
||||
|
||||
def get_url():
|
||||
timestamp = datetime.now(UTC).strftime('%Y.%m.%d %H:%M:%S %z').strip()
|
||||
state = str(uuid.uuid4())
|
||||
params = {
|
||||
'client_id': settings.ESIA_CLIENT_ID,
|
||||
'client_secret': '',
|
||||
'redirect_uri': settings.ESIA_REDIRECT_URI,
|
||||
'response_type': RESPONSE_CODE,
|
||||
'state': state,
|
||||
'timestamp': timestamp,
|
||||
'access_type': ACCESS_TYPE,
|
||||
'scope': ' '.join(SCOPES),
|
||||
}
|
||||
params['client_secret'] = sign_params(params)
|
||||
|
||||
return f'{settings.ESIA_BASE_URL}/aas/oauth2/ac?{urlencode(params)}'
|
||||
0
src/apps/esia/v1/__init__.py
Normal file
0
src/apps/esia/v1/__init__.py
Normal file
50
src/apps/esia/v1/router.py
Normal file
50
src/apps/esia/v1/router.py
Normal file
@ -0,0 +1,50 @@
|
||||
import secrets
|
||||
from logging import getLogger
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from apps.esia.sign import get_url
|
||||
from clients import clients as c
|
||||
from shared import exceptions as e
|
||||
from shared.redis import client as cache
|
||||
|
||||
from . import schema as s
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = APIRouter(
|
||||
prefix='/esia',
|
||||
tags=[
|
||||
'ESIA',
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@router.get('/login', response_model=s.LoginURL)
|
||||
async def login():
|
||||
url = get_url()
|
||||
return s.LoginURL(url=url)
|
||||
|
||||
|
||||
@router.post('/callback')
|
||||
async def callback(code: str):
|
||||
token = None
|
||||
for i in range(3):
|
||||
try:
|
||||
token = await c.esia_api.access_token(code)
|
||||
break
|
||||
except Exception:
|
||||
logger.warning(
|
||||
'Error occurred while accessing ESI API. Retrying...'
|
||||
)
|
||||
if i == 2:
|
||||
raise
|
||||
|
||||
if token is None:
|
||||
raise e.BadRequestException
|
||||
|
||||
await c.esia_api.get_user_info(token.access_token, token.id_token)
|
||||
|
||||
access_token = secrets.token_urlsafe(32)
|
||||
cache.set(access_token, access_token)
|
||||
|
||||
return s.Token(access_token=access_token)
|
||||
9
src/apps/esia/v1/schema.py
Normal file
9
src/apps/esia/v1/schema.py
Normal file
@ -0,0 +1,9 @@
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class LoginURL(TypedDict):
|
||||
url: str
|
||||
|
||||
|
||||
class Token(TypedDict):
|
||||
access_token: str
|
||||
0
src/apps/users/__init__.py
Normal file
0
src/apps/users/__init__.py
Normal file
20
src/apps/users/auth.py
Normal file
20
src/apps/users/auth.py
Normal file
@ -0,0 +1,20 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from shared import exceptions as e
|
||||
from shared.redis import client as cache
|
||||
|
||||
BEARER = HTTPBearer()
|
||||
|
||||
|
||||
async def login(
|
||||
credentials: Annotated[HTTPAuthorizationCredentials, Depends(BEARER)],
|
||||
):
|
||||
is_exist = cache.get(credentials.credentials)
|
||||
|
||||
if is_exist is None:
|
||||
raise e.UnauthorizedException
|
||||
|
||||
return True
|
||||
0
src/apps/users/v1/__init__.py
Normal file
0
src/apps/users/v1/__init__.py
Normal file
1113
src/apps/users/v1/mock.py
Normal file
1113
src/apps/users/v1/mock.py
Normal file
File diff suppressed because it is too large
Load Diff
152
src/apps/users/v1/router.py
Normal file
152
src/apps/users/v1/router.py
Normal file
@ -0,0 +1,152 @@
|
||||
from datetime import datetime
|
||||
from json import dumps
|
||||
from logging import getLogger
|
||||
from typing import Annotated, Any
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, status
|
||||
|
||||
from apps.users.auth import login
|
||||
from shared.redis import client as cache
|
||||
|
||||
from . import mock
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = APIRouter(
|
||||
prefix='/user',
|
||||
tags=[
|
||||
'User',
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@router.post('/measurement', status_code=status.HTTP_202_ACCEPTED)
|
||||
async def measurement(
|
||||
user: Annotated[str, Depends(login)],
|
||||
ad: Annotated[int, Body()],
|
||||
sd: Annotated[int, Body()],
|
||||
pulse: Annotated[int, Body()],
|
||||
created_at: Annotated[datetime, Body()],
|
||||
comment: Annotated[str, Body()],
|
||||
status: Annotated[str, Body()],
|
||||
):
|
||||
created = created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
data = {
|
||||
'ad': ad,
|
||||
'sd': sd,
|
||||
'pulse': pulse,
|
||||
'created_at': created,
|
||||
'comment': comment,
|
||||
'status': status,
|
||||
}
|
||||
cache_key = f'tdn:measurement:{user}:{created}'
|
||||
cache.set(cache_key, dumps(data))
|
||||
return
|
||||
|
||||
|
||||
@router.get('/measurements')
|
||||
async def measurements(user: Annotated[str, Depends(login)],):
|
||||
data = [cache.get(key) for key in cache.keys(f'tdn:measurement:{user}:*')]
|
||||
return data
|
||||
|
||||
|
||||
@router.get('/queue')
|
||||
async def queue(user: Annotated[bool, Depends(login)]):
|
||||
return {
|
||||
'id': 60,
|
||||
'guid': '92b3343d-1cb2-47b2-8497-a37e38b6ba24',
|
||||
'tmk_date': None,
|
||||
'created_at': '2025-04-02 15:21:19.890343',
|
||||
'code_mo': '166502',
|
||||
'mo_name': 'ГАУЗ "ГКБ№7 ИМ. М.Н.САДЫКОВА"',
|
||||
'doctor_spec': '109',
|
||||
'doctor_snils': None,
|
||||
'patient_name': 'Иванов Петр Федорович',
|
||||
'patient_birthday': '1997-03-01',
|
||||
'patient_snils': '099-678-666 12',
|
||||
'patient_policy': None,
|
||||
'patient_phone': '+79123456789',
|
||||
'patient_email': None,
|
||||
'tmk_status': 1,
|
||||
'tmk_status_name': 'Создана',
|
||||
'tmk_cancel_reason': None,
|
||||
'tmk_cancel_reason_name': None,
|
||||
'vks_doctor_link': None,
|
||||
'vks_patient_link': None,
|
||||
'doctor_spec_name': 'врач-терапевт',
|
||||
}
|
||||
|
||||
|
||||
@router.get('/getDepartments')
|
||||
async def get_departments():
|
||||
data: dict[Any, Any] = {}
|
||||
return data
|
||||
|
||||
|
||||
@router.get('/getSpecs')
|
||||
async def get_specs():
|
||||
return mock.specs
|
||||
|
||||
|
||||
@router.get('/findPat')
|
||||
async def find_pat(user: Annotated[str, Depends(login)]):
|
||||
return mock.findpat[0]
|
||||
|
||||
|
||||
@router.get('/getProfile')
|
||||
async def get_profile(user: Annotated[str, Depends(login)]):
|
||||
return mock.profile[0]
|
||||
|
||||
|
||||
@router.get('/getHosps')
|
||||
async def get_hosps():
|
||||
return mock.hosps
|
||||
|
||||
|
||||
@router.get('/getELNS')
|
||||
async def get_elns(user: Annotated[str, Depends(login)]):
|
||||
return mock.elns[0]
|
||||
|
||||
|
||||
@router.get('/getVaccsReport')
|
||||
async def get_vaccs_report(user: Annotated[str, Depends(login)]):
|
||||
return mock.vacs[0]
|
||||
|
||||
|
||||
@router.get('/getDiagnosticResults')
|
||||
async def get_diagnostic_results(user: Annotated[str, Depends(login)]):
|
||||
return mock.diagnosticResults[0]
|
||||
|
||||
|
||||
@router.get('/getCurrHosp')
|
||||
async def get_curr_hosp(user: Annotated[str, Depends(login)]):
|
||||
return mock.currHosp[0]
|
||||
|
||||
|
||||
@router.get('/getPatFLG')
|
||||
async def get_pat_flg(user: Annotated[str, Depends(login)]):
|
||||
return mock.patFLG[0]
|
||||
|
||||
|
||||
@router.get('/getEntries')
|
||||
async def get_entries(user: Annotated[str, Depends(login)]):
|
||||
return mock.entries[0]
|
||||
|
||||
|
||||
@router.get('/getRoutesList')
|
||||
async def get_routes_list(user: Annotated[str, Depends(login)]):
|
||||
return mock.routesList[0]
|
||||
|
||||
|
||||
@router.get('/getMedExamDict')
|
||||
async def get_med_exam_dict(user: Annotated[str, Depends(login)]):
|
||||
return mock.medexamDict
|
||||
|
||||
|
||||
@router.get('/getHospRecommendations')
|
||||
async def get_hosp_recommendations(user: Annotated[str, Depends(login)]):
|
||||
return mock.hospRecommendations
|
||||
|
||||
|
||||
@router.get('/getHospRoutes')
|
||||
async def get_hosp_routes(user: Annotated[str, Depends(login)]):
|
||||
return mock.hospRoutes
|
||||
Reference in New Issue
Block a user