Initial commit: site monorepo with API, web, and infra.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
влад
2026-07-16 10:11:54 +03:00
co-authored by Cursor
commit 016910ffb7
447 changed files with 73972 additions and 0 deletions
+394
View File
@@ -0,0 +1,394 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from uuid import uuid4
from sqlalchemy import func, select
from app.core.crypto import hash_opaque_token
from app.core.database import session_scope
from app.modules.sync.models import (
Enterprise,
EnterpriseMember,
FarmHub,
HubCredential,
HubPairingSession,
SyncAppliedEvent,
SyncConflict,
SyncCursor,
SyncEventLog,
SyncOutbox,
SyncRecordState,
UserFarmAccess,
)
def _detach(db, instance):
db.refresh(instance)
db.expunge(instance)
return instance
def get_enterprise_by_id(enterprise_id: str) -> Enterprise | None:
with session_scope() as db:
row = db.get(Enterprise, enterprise_id)
return _detach(db, row) if row else None
def get_enterprise_by_slug(slug: str) -> Enterprise | None:
with session_scope() as db:
row = db.scalar(select(Enterprise).where(Enterprise.slug == slug))
return _detach(db, row) if row else None
def create_enterprise(name: str, slug: str) -> Enterprise:
with session_scope() as db:
row = Enterprise(name=name, slug=slug, status="active")
db.add(row)
db.flush()
return _detach(db, row)
def get_member(user_id: str, enterprise_id: str) -> EnterpriseMember | None:
with session_scope() as db:
row = db.scalar(
select(EnterpriseMember).where(
EnterpriseMember.user_id == user_id,
EnterpriseMember.enterprise_id == enterprise_id,
)
)
return _detach(db, row) if row else None
def add_member(user_id: str, enterprise_id: str, role: str) -> EnterpriseMember:
with session_scope() as db:
row = EnterpriseMember(user_id=user_id, enterprise_id=enterprise_id, role=role)
db.add(row)
db.flush()
return _detach(db, row)
def list_members(enterprise_id: str) -> list[EnterpriseMember]:
with session_scope() as db:
rows = list(
db.scalars(
select(EnterpriseMember).where(EnterpriseMember.enterprise_id == enterprise_id)
)
)
return [_detach(db, row) for row in rows]
def set_member_role(user_id: str, enterprise_id: str, role: str) -> EnterpriseMember | None:
with session_scope() as db:
row = db.scalar(
select(EnterpriseMember).where(
EnterpriseMember.user_id == user_id,
EnterpriseMember.enterprise_id == enterprise_id,
)
)
if not row:
return None
row.role = role
db.flush()
return _detach(db, row)
def list_farm_access(user_id: str, enterprise_id: str) -> list[str]:
with session_scope() as db:
rows = db.scalars(
select(UserFarmAccess.farm_hub_id)
.join(FarmHub, FarmHub.id == UserFarmAccess.farm_hub_id)
.where(UserFarmAccess.user_id == user_id, FarmHub.enterprise_id == enterprise_id)
).all()
return list(rows)
def grant_farm_access(user_id: str, farm_hub_id: str) -> UserFarmAccess:
with session_scope() as db:
existing = db.scalar(
select(UserFarmAccess).where(
UserFarmAccess.user_id == user_id,
UserFarmAccess.farm_hub_id == farm_hub_id,
)
)
if existing:
return _detach(db, existing)
row = UserFarmAccess(user_id=user_id, farm_hub_id=farm_hub_id)
db.add(row)
db.flush()
return _detach(db, row)
def get_farm_hub_by_site_id(hub_site_id: str) -> FarmHub | None:
with session_scope() as db:
row = db.scalar(select(FarmHub).where(FarmHub.hub_site_id == hub_site_id))
return _detach(db, row) if row else None
def get_farm_hub_by_id(farm_hub_id: str) -> FarmHub | None:
with session_scope() as db:
row = db.get(FarmHub, farm_hub_id)
return _detach(db, row) if row else None
def list_farm_hubs(enterprise_id: str) -> list[FarmHub]:
with session_scope() as db:
rows = list(db.scalars(select(FarmHub).where(FarmHub.enterprise_id == enterprise_id).order_by(FarmHub.name)))
return [_detach(db, row) for row in rows]
def create_pairing_session(enterprise_id: str, code: str, code_hash: str, ttl_minutes: int = 15) -> HubPairingSession:
with session_scope() as db:
row = HubPairingSession(
enterprise_id=enterprise_id,
code=code,
code_hash=code_hash,
expires_at=datetime.now(UTC) + timedelta(minutes=ttl_minutes),
)
db.add(row)
db.flush()
return _detach(db, row)
def get_pairing_session_by_code_hash(code_hash: str) -> HubPairingSession | None:
with session_scope() as db:
row = db.scalar(
select(HubPairingSession).where(
HubPairingSession.code_hash == code_hash,
HubPairingSession.confirmed_at.is_(None),
)
)
return _detach(db, row) if row else None
def confirm_pairing_session(session_id: str, farm_hub_id: str) -> None:
with session_scope() as db:
row = db.get(HubPairingSession, session_id)
if row:
row.confirmed_at = datetime.now(UTC)
row.farm_hub_id = farm_hub_id
def create_farm_hub(
enterprise_id: str,
name: str,
hub_site_id: str,
url: str | None = None,
) -> FarmHub:
with session_scope() as db:
row = FarmHub(
enterprise_id=enterprise_id,
name=name,
hub_site_id=hub_site_id,
url=url,
status="active",
)
db.add(row)
db.flush()
return _detach(db, row)
def create_hub_credential(farm_hub_id: str, api_key: str) -> HubCredential:
from app.core.crypto import hash_opaque_token as _hash
with session_scope() as db:
row = HubCredential(farm_hub_id=farm_hub_id, secret_hash=_hash(api_key))
db.add(row)
db.flush()
return _detach(db, row)
def verify_hub_credential(hub_site_id: str, api_key: str) -> FarmHub | None:
from app.core.crypto import hash_opaque_token as _hash
key_hash = _hash(api_key)
with session_scope() as db:
row = db.scalar(
select(FarmHub)
.join(HubCredential, HubCredential.farm_hub_id == FarmHub.id)
.where(
FarmHub.hub_site_id == hub_site_id,
HubCredential.secret_hash == key_hash,
HubCredential.revoked_at.is_(None),
)
)
return _detach(db, row) if row else None
def update_hub_heartbeat(farm_hub_id: str, wesp_version: str | None) -> None:
with session_scope() as db:
row = db.get(FarmHub, farm_hub_id)
if row:
row.last_seen = datetime.now(UTC)
if wesp_version:
row.wesp_version = wesp_version
def next_seq(enterprise_id: str) -> int:
with session_scope() as db:
current = db.scalar(
select(func.max(SyncEventLog.seq)).where(SyncEventLog.enterprise_id == enterprise_id)
)
return int(current or 0) + 1
def event_exists(event_id: str) -> bool:
with session_scope() as db:
row = db.scalar(select(SyncEventLog.id).where(SyncEventLog.event_id == event_id))
return row is not None
def append_event_log(**kwargs) -> SyncEventLog:
with session_scope() as db:
row = SyncEventLog(**kwargs)
db.add(row)
db.flush()
return _detach(db, row)
def applied_event_exists(site_id: str, event_id: str) -> bool:
with session_scope() as db:
row = db.scalar(
select(SyncAppliedEvent.id).where(
SyncAppliedEvent.site_id == site_id,
SyncAppliedEvent.event_id == event_id,
)
)
return row is not None
def mark_applied(enterprise_id: str, site_id: str, event_id: str) -> None:
with session_scope() as db:
db.add(
SyncAppliedEvent(
enterprise_id=enterprise_id,
site_id=site_id,
event_id=event_id,
)
)
def get_record_state(enterprise_id: str, table_name: str, record_id: str) -> SyncRecordState | None:
with session_scope() as db:
row = db.scalar(
select(SyncRecordState).where(
SyncRecordState.enterprise_id == enterprise_id,
SyncRecordState.table_name == table_name,
SyncRecordState.record_id == record_id,
)
)
return _detach(db, row) if row else None
def upsert_record_state(
enterprise_id: str,
table_name: str,
record_id: str,
agreed_version: int,
agreed_hash: str,
last_event_id: str,
) -> None:
with session_scope() as db:
row = db.scalar(
select(SyncRecordState).where(
SyncRecordState.enterprise_id == enterprise_id,
SyncRecordState.table_name == table_name,
SyncRecordState.record_id == record_id,
)
)
if row:
row.agreed_version = agreed_version
row.agreed_hash = agreed_hash
row.last_event_id = last_event_id
row.updated_at = datetime.now(UTC)
else:
db.add(
SyncRecordState(
enterprise_id=enterprise_id,
table_name=table_name,
record_id=record_id,
agreed_version=agreed_version,
agreed_hash=agreed_hash,
last_event_id=last_event_id,
)
)
def list_conflicts(enterprise_id: str, status: str = "pending") -> list[SyncConflict]:
with session_scope() as db:
rows = list(
db.scalars(
select(SyncConflict)
.where(SyncConflict.enterprise_id == enterprise_id, SyncConflict.status == status)
.order_by(SyncConflict.created_at.desc())
)
)
return [_detach(db, row) for row in rows]
def get_conflict(conflict_id: str) -> SyncConflict | None:
with session_scope() as db:
row = db.get(SyncConflict, conflict_id)
return _detach(db, row) if row else None
def get_or_create_cursor(enterprise_id: str, farm_hub_id: str, direction: str) -> SyncCursor:
with session_scope() as db:
row = db.scalar(
select(SyncCursor).where(
SyncCursor.farm_hub_id == farm_hub_id,
SyncCursor.direction == direction,
)
)
if row:
return _detach(db, row)
row = SyncCursor(
enterprise_id=enterprise_id,
farm_hub_id=farm_hub_id,
direction=direction,
last_acked_seq=0,
last_pulled_seq=0,
)
db.add(row)
db.flush()
return _detach(db, row)
def update_cursor_ack(farm_hub_id: str, direction: str, last_acked_seq: int) -> None:
with session_scope() as db:
row = db.scalar(
select(SyncCursor).where(
SyncCursor.farm_hub_id == farm_hub_id,
SyncCursor.direction == direction,
)
)
if row:
row.last_acked_seq = max(row.last_acked_seq, last_acked_seq)
row.updated_at = datetime.now(UTC)
def pull_events_since(
enterprise_id: str,
cursor: int,
limit: int,
*,
exclude_origin_site_id: str | None = None,
) -> list[SyncEventLog]:
with session_scope() as db:
query = select(SyncEventLog).where(
SyncEventLog.enterprise_id == enterprise_id,
SyncEventLog.seq > cursor,
)
if exclude_origin_site_id:
query = query.where(
(SyncEventLog.origin_site_id.is_(None))
| (SyncEventLog.origin_site_id != exclude_origin_site_id)
)
rows = list(db.scalars(query.order_by(SyncEventLog.seq.asc()).limit(limit)))
return [_detach(db, row) for row in rows]
def get_farm_hub_site_id(farm_hub_id: str) -> str | None:
with session_scope() as db:
row = db.get(FarmHub, farm_hub_id)
return row.hub_site_id if row else None