mirror of
https://github.com/openmax-server/server.git
synced 2026-05-26 13:31:43 +03:00
feat: 23 опкод для регистрации, смс шлюз, докер
This commit is contained in:
10
sms-gateway/app/providers/__init__.py
Normal file
10
sms-gateway/app/providers/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@dataclass
|
||||
class SendResult:
|
||||
success: bool
|
||||
provider: str
|
||||
code: str | None = None
|
||||
raw_response: dict = field(default_factory=dict)
|
||||
error: str | None = None
|
||||
10
sms-gateway/app/providers/base.py
Normal file
10
sms-gateway/app/providers/base.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
from app.providers import SendResult
|
||||
|
||||
class BaseProvider(ABC):
|
||||
name: str = "base"
|
||||
|
||||
@abstractmethod
|
||||
async def send(self, phone_number: str, code: str | None = None) -> SendResult:
|
||||
pass
|
||||
36
sms-gateway/app/providers/lk_api.py
Normal file
36
sms-gateway/app/providers/lk_api.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import random
|
||||
import uuid
|
||||
from app.config import ProviderConfig
|
||||
from app.providers import SendResult
|
||||
from app.providers.base import BaseProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class LkApiProvider(BaseProvider):
|
||||
"""
|
||||
Внутренний провайдер — SMS не шлёт.
|
||||
Генерирует код, который отображается в личном кабинете.
|
||||
Используется для всех стран кроме России.
|
||||
"""
|
||||
name = "lk_api"
|
||||
|
||||
def __init__(self, config: ProviderConfig | None = None) -> None:
|
||||
pass
|
||||
|
||||
async def send(self, phone_number: str, code: str | None = None) -> SendResult:
|
||||
normalized = phone_number if phone_number.startswith("+") else f"+{phone_number}"
|
||||
if not code:
|
||||
code = str(random.randint(10000, 99999))
|
||||
request_uuid = str(uuid.uuid4())
|
||||
logger.info(
|
||||
"lk_api: код для ЛК | phone=%s code=%s uuid=%s",
|
||||
normalized, code, request_uuid,
|
||||
)
|
||||
return SendResult(
|
||||
success=True,
|
||||
provider=self.name,
|
||||
code=code,
|
||||
raw_response={"code": int(code), "uuid": request_uuid, "note": "displayed in personal cabinet"},
|
||||
)
|
||||
34
sms-gateway/app/providers/registry.py
Normal file
34
sms-gateway/app/providers/registry.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from app.config import Config, ProviderConfig
|
||||
from app.providers.base import BaseProvider
|
||||
from app.providers.lk_api import LkApiProvider
|
||||
from app.providers.sms_api import SmsApiProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
PROVIDER_REGISTRY: dict[str, type[BaseProvider]] = {
|
||||
"sms_api": SmsApiProvider,
|
||||
"lk_api": LkApiProvider,
|
||||
}
|
||||
|
||||
def build_provider(name: str, config: ProviderConfig) -> BaseProvider | None:
|
||||
cls = PROVIDER_REGISTRY.get(config.type)
|
||||
if cls is None:
|
||||
logger.error("Неизвестный тип провайдера: %s", config.type)
|
||||
return None
|
||||
if not config.enabled:
|
||||
logger.debug("Провайдер %s отключён", name)
|
||||
return None
|
||||
return cls(config)
|
||||
|
||||
def build_all_providers(config: Config) -> dict[str, BaseProvider]:
|
||||
result: dict[str, BaseProvider] = {}
|
||||
for name, provider_cfg in config.providers.items():
|
||||
provider = build_provider(name, provider_cfg)
|
||||
if provider is not None:
|
||||
result[name] = provider
|
||||
logger.info("Провайдер загружен: %s (тип: %s)", name, provider_cfg.type)
|
||||
if "lk_api" not in result:
|
||||
result["lk_api"] = LkApiProvider()
|
||||
logger.info("lk_api добавлен как fallback по умолчанию")
|
||||
return result
|
||||
52
sms-gateway/app/providers/sms_api.py
Normal file
52
sms-gateway/app/providers/sms_api.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import httpx
|
||||
from app.config import ProviderConfig
|
||||
from app.providers import SendResult
|
||||
from app.providers.base import BaseProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SmsApiProvider(BaseProvider):
|
||||
"""
|
||||
Внешний SMS-сервис.
|
||||
Отправляет реальное SMS, возвращает код и uuid.
|
||||
Используется для России (+7).
|
||||
"""
|
||||
name = "sms_api"
|
||||
|
||||
def __init__(self, config: ProviderConfig) -> None:
|
||||
extra = config.extra()
|
||||
self.base_url: str = extra.get("base_url", "").rstrip("/")
|
||||
self.send_endpoint: str = extra.get("send_endpoint", "/auth/code")
|
||||
self.timeout: int = int(extra.get("timeout", 10))
|
||||
|
||||
async def send(self, phone_number: str, code: str | None = None) -> SendResult:
|
||||
normalized = phone_number if phone_number.startswith("+") else f"+{phone_number}"
|
||||
url = f"{self.base_url}{self.send_endpoint}"
|
||||
payload: dict = {"phone_number": normalized}
|
||||
if code:
|
||||
payload["code"] = code
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
url,
|
||||
json=payload,
|
||||
headers={"accept": "application/json", "Content-Type": "application/json"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
code = str(data.get("code", ""))
|
||||
logger.info("sms_api: SMS отправлен на %s | uuid=%s code=%s", normalized, data.get("uuid"), code)
|
||||
return SendResult(
|
||||
success=True,
|
||||
provider=self.name,
|
||||
code=code,
|
||||
raw_response=data,
|
||||
)
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.error("sms_api HTTP %s для %s: %s", e.response.status_code, normalized, e)
|
||||
return SendResult(success=False, provider=self.name, error=str(e))
|
||||
except Exception as e:
|
||||
logger.error("sms_api ошибка для %s: %s", normalized, e)
|
||||
return SendResult(success=False, provider=self.name, error=str(e))
|
||||
Reference in New Issue
Block a user