Проект разделен на два репозитория

This commit is contained in:
mi
2026-08-14 15:42:45 +03:00
parent e06a77ee1d
commit bbef7a30c9
521 changed files with 2597 additions and 2302 deletions
@@ -0,0 +1 @@
"""HAN Bitrix24 Open Lines adapter."""
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,188 @@
from __future__ import annotations
import uuid
from datetime import UTC, datetime
from sqlalchemy import (
BigInteger,
DateTime,
ForeignKey,
Index,
Integer,
JSON,
String,
Text,
UniqueConstraint,
text,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
SCHEMA = "bitrix_local"
def now() -> datetime:
return datetime.now(UTC)
class Base(DeclarativeBase):
pass
class Common:
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now, onupdate=now)
record_status: Mapped[str] = mapped_column(String(1), default="A")
status_changed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
status_change_reason: Mapped[str | None] = mapped_column(String(255))
class PortalInstallation(Common, Base):
__tablename__ = "portal_installations"
__table_args__ = (
Index(
"uq_portal_active_domain",
"domain",
unique=True,
postgresql_where=text("record_status = 'A'"),
),
{"schema": SCHEMA},
)
member_id: Mapped[str] = mapped_column(String(128), unique=True)
domain: Mapped[str] = mapped_column(String(255))
client_endpoint: Mapped[str] = mapped_column(String(1024))
access_ciphertext: Mapped[str] = mapped_column(Text)
access_nonce: Mapped[str] = mapped_column(String(64))
refresh_ciphertext: Mapped[str] = mapped_column(Text)
refresh_nonce: Mapped[str] = mapped_column(String(64))
application_ciphertext: Mapped[str] = mapped_column(Text)
application_nonce: Mapped[str] = mapped_column(String(64))
key_version: Mapped[str] = mapped_column(String(32))
expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
scope: Mapped[str | None] = mapped_column(Text)
install_status: Mapped[str] = mapped_column(String(32), default="installed")
setup_status: Mapped[str] = mapped_column(String(32), default="pending")
last_refresh_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_error_code: Mapped[str | None] = mapped_column(String(64))
class ConnectorSetup(Common, Base):
__tablename__ = "connector_setup"
__table_args__ = (
UniqueConstraint("portal_id", "connector_id", "line_id"),
Index("ix_setup_retry", "next_retry_at", "attempt_count"),
{"schema": SCHEMA},
)
portal_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey(f"{SCHEMA}.portal_installations.id")
)
connector_id: Mapped[str] = mapped_column(String(64))
line_id: Mapped[str] = mapped_column(String(32))
registered: Mapped[bool] = mapped_column(default=False)
activated: Mapped[bool] = mapped_column(default=False)
bindings_json: Mapped[dict] = mapped_column(JSON, default=dict)
desired_version: Mapped[str] = mapped_column(String(32), default="1")
observed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
next_retry_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
attempt_count: Mapped[int] = mapped_column(Integer, default=0)
lease_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_error_code: Mapped[str | None] = mapped_column(String(64))
class DialogSession(Common, Base):
__tablename__ = "dialog_sessions"
__table_args__ = (
Index(
"uq_dialog_active_external_chat",
"external_chat_id",
unique=True,
postgresql_where=text("record_status = 'A'"),
),
Index("ix_dialog_bitrix_chat", "bitrix_chat_id"),
Index("ix_dialog_session", "session_id"),
Index("ix_dialog_status_updated", "status", "updated_at"),
{"schema": SCHEMA},
)
external_chat_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True))
bitrix_chat_id: Mapped[int | None] = mapped_column(BigInteger)
session_id: Mapped[str | None] = mapped_column(String(255))
portal_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey(f"{SCHEMA}.portal_installations.id")
)
status: Mapped[str] = mapped_column(String(16), default="open")
class InboxEvent(Common, Base):
__tablename__ = "inbox_events"
__table_args__ = (
UniqueConstraint("event_id"),
Index(
"uq_inbox_chat_message",
"external_chat_id",
"bitrix_message_id",
unique=True,
postgresql_where=text("bitrix_message_id IS NOT NULL"),
),
Index("ix_inbox_worker", "status", "next_attempt_at"),
{"schema": SCHEMA},
)
event_id: Mapped[str] = mapped_column(String(255))
event_type: Mapped[str] = mapped_column(String(64))
external_chat_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True))
bitrix_message_id: Mapped[str | None] = mapped_column(String(255))
payload_fingerprint: Mapped[str] = mapped_column(String(64))
normalized_json: Mapped[dict] = mapped_column(JSON)
status: Mapped[str] = mapped_column(String(32), default="received")
attempt_count: Mapped[int] = mapped_column(Integer, default=0)
next_attempt_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now)
lease_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
api_ack_status: Mapped[str | None] = mapped_column(String(32))
delivery_ack_status: Mapped[str | None] = mapped_column(String(32))
last_error_code: Mapped[str | None] = mapped_column(String(64))
class OutboundMessage(Common, Base):
__tablename__ = "outbound_messages"
__table_args__ = (Index("ix_outbound_worker", "status", "next_attempt_at"), {"schema": SCHEMA})
message_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), unique=True)
external_chat_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True))
request_fingerprint: Mapped[str] = mapped_column(String(64))
payload_json: Mapped[dict] = mapped_column(JSON)
status: Mapped[str] = mapped_column(String(32), default="received")
bitrix_message_id: Mapped[str | None] = mapped_column(String(255))
response_json: Mapped[dict | None] = mapped_column(JSON)
attempt_count: Mapped[int] = mapped_column(Integer, default=0)
next_attempt_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now)
lease_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_error_code: Mapped[str | None] = mapped_column(String(64))
class DeliveryAckOutbox(Common, Base):
__tablename__ = "delivery_ack_outbox"
__table_args__ = (Index("ix_ack_worker", "status", "next_attempt_at"), {"schema": SCHEMA})
inbox_event_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey(f"{SCHEMA}.inbox_events.id"), unique=True
)
payload_json: Mapped[dict] = mapped_column(JSON)
status: Mapped[str] = mapped_column(String(32), default="pending")
attempt_count: Mapped[int] = mapped_column(Integer, default=0)
next_attempt_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now)
lease_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_error_code: Mapped[str | None] = mapped_column(String(64))
class InstallRun(Common, Base):
__tablename__ = "install_runs"
__table_args__ = ({"schema": SCHEMA},)
portal_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True))
status: Mapped[str] = mapped_column(String(32))
result_json: Mapped[dict] = mapped_column(JSON, default=dict)
class AuditEvent(Common, Base):
__tablename__ = "audit_events"
__table_args__ = (Index("ix_audit_created", "created_at"), {"schema": SCHEMA})
event_type: Mapped[str] = mapped_column(String(128))
actor_type: Mapped[str] = mapped_column(String(32), default="system")
safe_details: Mapped[dict] = mapped_column(JSON, default=dict)
@@ -0,0 +1,25 @@
from __future__ import annotations
from typing import Any
import asyncpg
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
def asyncpg_dsn(url: str) -> str:
if url.startswith("postgresql+asyncpg://"):
return url.replace("postgresql+asyncpg://", "postgresql://", 1)
return url
def create_postgres_engine(url: str, **engine_options: Any) -> AsyncEngine:
dsn = asyncpg_dsn(url)
async def connect():
return await asyncpg.connect(dsn=dsn)
return create_async_engine(
"postgresql+asyncpg://",
async_creator=connect,
**engine_options,
)