Add Dispatch_V0.1.1
This commit is contained in:
180
Dispatch_V0.1.1/auth_service.py
Normal file
180
Dispatch_V0.1.1/auth_service.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# hub/my_account/auth_service.py
|
||||
|
||||
"""Сервис аутентификации Dispatch: проверка логина/пароля и запись сессии.
|
||||
|
||||
Назначение модуля:
|
||||
Полностью повторяет контракт сервиса USMS `hub/my_account/auth_service`,
|
||||
но обращается к локальному каталогу `DB_dispatch` независимого
|
||||
приложения Dispatch. Источники данных:
|
||||
|
||||
- `DB_dispatch/0_users.py` — список учётных записей диспетчеров
|
||||
и руководителей сервисной службы;
|
||||
- `DB_dispatch/1_actual_state.py` — текущая активная сессия.
|
||||
|
||||
Архитектурные ограничения:
|
||||
- Каталог `DB_dispatch` располагается на одном уровне с каталогом
|
||||
`dispatch`, поэтому путь вычисляется относительно текущего файла.
|
||||
- Файлы данных читаются через `importlib.util` без подключения
|
||||
к фреймворку USMS. Это сохраняет независимость дистрибутива.
|
||||
- Запись сессии перезаписывает файл целиком, без частичных правок.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
def _resolve_db_dir() -> str:
|
||||
"""Вернуть абсолютный путь к каталогу `DB_dispatch`.
|
||||
|
||||
Структура размещения:
|
||||
<project_root>/dispatch/hub/my_account/auth_service.py
|
||||
<project_root>/DB_dispatch/0_users.py
|
||||
Поэтому переход — четыре уровня вверх от текущего файла, затем
|
||||
спуск в каталог `DB_dispatch`.
|
||||
"""
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.dirname(here)))
|
||||
return os.path.join(project_root, "DB_dispatch")
|
||||
|
||||
|
||||
_DB_DIR = _resolve_db_dir()
|
||||
|
||||
|
||||
def _load_users() -> list[dict]:
|
||||
"""Загрузить список пользователей из `DB_dispatch/0_users.py`."""
|
||||
path = os.path.join(_DB_DIR, "0_users.py")
|
||||
spec = importlib.util.spec_from_file_location("_dispatch_db_users", path)
|
||||
if spec is None or spec.loader is None:
|
||||
return []
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return list(getattr(mod, "users", []))
|
||||
|
||||
|
||||
def load_facility_locations() -> dict[str, str]:
|
||||
"""Загрузить таблицу `facility_id → строка локации` из `DB_dispatch/2_customer_facility_list.py`."""
|
||||
path = os.path.join(_DB_DIR, "2_customer_facility_list.py")
|
||||
if not os.path.exists(path):
|
||||
return {}
|
||||
spec = importlib.util.spec_from_file_location("_dispatch_db_facilities", path)
|
||||
if spec is None or spec.loader is None:
|
||||
return {}
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
raw = getattr(mod, "DEFAULT_BUTTON_LOCATIONS", {})
|
||||
return {str(k): str(v) for k, v in dict(raw).items()}
|
||||
|
||||
|
||||
def load_software_list() -> dict[str, str]:
|
||||
"""Загрузить таблицу `software_id → наименование` из `DB_dispatch/3_software_list.py`."""
|
||||
path = os.path.join(_DB_DIR, "3_software_list.py")
|
||||
if not os.path.exists(path):
|
||||
return {}
|
||||
spec = importlib.util.spec_from_file_location("_dispatch_db_software", path)
|
||||
if spec is None or spec.loader is None:
|
||||
return {}
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
raw = getattr(mod, "software_list_ru", {})
|
||||
return {str(k): str(v) for k, v in dict(raw).items()}
|
||||
|
||||
|
||||
def load_malfunction_list() -> dict[str, list[str]]:
|
||||
"""Загрузить таблицу `software_id → список заголовков` из `DB_dispatch/4_malfunction_list.py`."""
|
||||
path = os.path.join(_DB_DIR, "4_malfunction_list.py")
|
||||
if not os.path.exists(path):
|
||||
return {}
|
||||
spec = importlib.util.spec_from_file_location("_dispatch_db_malfunction", path)
|
||||
if spec is None or spec.loader is None:
|
||||
return {}
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
raw = getattr(mod, "malfunction_list_ru", {})
|
||||
return {str(k): [str(item) for item in list(values)] for k, values in dict(raw).items()}
|
||||
|
||||
|
||||
def get_user_facility(user: dict | None) -> str:
|
||||
"""Вернуть строку локации, привязанную к пользователю.
|
||||
|
||||
Источник связи — поле `facility_id` в `DB_dispatch/0_users.py` и
|
||||
реестр `DEFAULT_BUTTON_LOCATIONS` из `DB_dispatch/2_customer_facility_list.py`.
|
||||
Возвращает пустую строку, если пользователь не передан или связь
|
||||
не настроена.
|
||||
"""
|
||||
if not user:
|
||||
return ""
|
||||
facility_id = str(user.get("facility_id", "")).strip()
|
||||
if not facility_id:
|
||||
return ""
|
||||
locations = load_facility_locations()
|
||||
return locations.get(facility_id, "")
|
||||
|
||||
|
||||
def authenticate(login: str, password: str) -> tuple[dict | None, str]:
|
||||
"""Проверить пару логин/пароль.
|
||||
|
||||
Возвращает ``(user_dict, "")`` при успехе или ``(None, error_code)``:
|
||||
* ``"no_user"`` — логин не найден;
|
||||
* ``"bad_password"`` — логин верный, пароль не совпадает.
|
||||
"""
|
||||
users = _load_users()
|
||||
matched = [u for u in users if u.get("login") == login]
|
||||
if not matched:
|
||||
return None, "no_user"
|
||||
if matched[0].get("password") == password:
|
||||
return matched[0], ""
|
||||
return None, "bad_password"
|
||||
|
||||
|
||||
def write_session(user: dict) -> dict:
|
||||
"""Записать сессию в `DB_dispatch/1_actual_state.py` и вернуть её."""
|
||||
state = {
|
||||
"state_id": str(uuid.uuid4()),
|
||||
"user_id": user["user_id"],
|
||||
"is_online": True,
|
||||
"current_module": "dispatch",
|
||||
"last_activity_ts": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
|
||||
path = os.path.join(_DB_DIR, "1_actual_state.py")
|
||||
with open(path, "w", encoding="utf-8") as fh:
|
||||
fh.write("# -*- coding: utf-8 -*-\n")
|
||||
fh.write("# DB_dispatch/1_actual_state.py\n\n")
|
||||
fh.write(f"actual_state = [{repr(state)}]\n")
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def clear_session() -> None:
|
||||
"""Сбросить активную сессию (выход из системы)."""
|
||||
path = os.path.join(_DB_DIR, "1_actual_state.py")
|
||||
with open(path, "w", encoding="utf-8") as fh:
|
||||
fh.write("# -*- coding: utf-8 -*-\n")
|
||||
fh.write("# DB_dispatch/1_actual_state.py\n\n")
|
||||
fh.write("actual_state = []\n")
|
||||
|
||||
|
||||
def load_session() -> dict | None:
|
||||
"""Прочитать активную сессию и вернуть user-dict или None."""
|
||||
path = os.path.join(_DB_DIR, "1_actual_state.py")
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
spec = importlib.util.spec_from_file_location("_dispatch_db_state", path)
|
||||
if spec is None or spec.loader is None:
|
||||
return None
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
states = getattr(mod, "actual_state", [])
|
||||
if not states:
|
||||
return None
|
||||
user_id = states[0].get("user_id")
|
||||
if not user_id:
|
||||
return None
|
||||
users = _load_users()
|
||||
matched = [u for u in users if u.get("user_id") == user_id]
|
||||
return matched[0] if matched else None
|
||||
Reference in New Issue
Block a user