first commit
Some checks failed
Build And Push / publish (push) Failing after 3m15s

This commit is contained in:
2025-09-24 04:11:55 +03:00
commit 967bb8d936
45 changed files with 2651 additions and 0 deletions

View File

10
src/apps/esia/scopes.py Normal file
View File

@ -0,0 +1,10 @@
SCOPES = [
'openid',
'fullname',
# 'email',
# 'birthdate',
# 'gender',
# 'snils',
# 'id_doc',
# 'mobile',
]

82
src/apps/esia/sign.py Normal file
View 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)}'

View File

View 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)

View File

@ -0,0 +1,9 @@
from typing import TypedDict
class LoginURL(TypedDict):
url: str
class Token(TypedDict):
access_token: str

View File

20
src/apps/users/auth.py Normal file
View 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

View 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
View 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