Files
smart-org/apps/api/app/interfaces/realtime/rpc_app.py
T
mjalmoudy94 6937b88ca7 Merge origin/dev: لایهٔ realtime (WebSocket/RPC) + فلو دعوت عضو
ادغام ۱۲ کامیت ریموت (لایهٔ realtime روی WebSocket، حذف REST عمومی، رفع
باگ‌های صفحهٔ مدل‌ها) با ۸ کامیت محلی (ارائه‌دهندهٔ agy + رفع فلو دعوت).

تنها کانفلیکت واقعی در `app/main.py` بود و ماهوی بود، نه متنی:
ریموت اپ عمومی را به «فقط /health + /api/ws» کاهش داده و همهٔ هندلرهای HTTP
را به اپ داخلی `interfaces/realtime/rpc_app.py` منتقل کرده است. بنابراین
روتر `invites` من نباید در اپ عمومی mount شود.

راه‌حل:
- `app/main.py`: فقط `health` و `realtime_router` می‌مانند؛ ایمپورت‌های اضافی
  حذف شدند (نسخهٔ ریموت).
- `interfaces/realtime/rpc_app.py`: روتر `invites` به اپ داخلی اضافه شد تا
  `GET /api/invites/{token}` و `POST /api/invites/accept` از مسیر WS-RPC
  قابل دسترس باشند.

`api.ts` و `contracts/index.ts` خودکار و بدون کانفلیکت ادغام شدند: کلاینت
`previewInvite`/`acceptInvite` حالا از `getRealtimeClient().rpc()` رد می‌شود،
پس هیچ تغییری در فراخوانی‌ها لازم نبود.

رفع تست شکستهٔ ریموت: `src/test/version.test.tsx` مقدار `AppShell` را بدون
`RealtimeProvider` رندر می‌کرد و `useRealtime` استثنا می‌داد. حالا اسکلت
همان‌طور که در `app/layout.tsx` سیم‌کشی شده رندر می‌شود و سوکت در تست stub
شده است.

اعتبارسنجی روی درخت ادغام‌شده:
- بک‌اند ۲۷۰ تست سبز (شامل ۶ تست دعوت روی معماری جدید)؛ mypy تمیز.
- فرانت ۶۱ تست سبز؛ tsc و eslint تمیز.
- تأیید شد روترهای invite روی اپ داخلی RPC حاضرند و سطح عمومی تغییر نکرده.

نکته: ۵ خطای ruff در `tests/conftest.py`، `tests/test_error_log.py` و
`tests/test_realtime.py` از خود `origin/dev` می‌آیند و به این ادغام ربطی
ندارند؛ دست‌نخورده رها شدند.
2026-10-13 18:09:03 +03:30

82 lines
3.1 KiB
Python

"""Internal, never-socket-bound FastAPI app that holds every HTTP handler.
The public app (``app.main.create_app``) exposes only ``/health`` and the
``/api/ws`` WebSocket. All request/response logic lives here and is reached in
exactly one of two ways:
* from the WS-RPC proxy (``app.interfaces.realtime.router``), which dispatches
client RPCs into this app over an in-process ASGI transport, and
* from the test-suite's ``AsyncClient``/``TestClient``.
It is intentionally never bound to a socket, so including the routers here adds no
public REST surface — the only public routes are the ones ``create_app``
registers. Keeping the handlers in one FastAPI app also means they run through the
real middleware, dependencies, validation and error handling, unchanged.
"""
from __future__ import annotations
from fastapi import FastAPI
from app.core.config import settings
from app.interfaces.http.errors import register_exception_handlers
from app.interfaces.http.request_context import RequestContextMiddleware
from app.interfaces.http.routers import (
admin,
agy,
auth,
billing,
health,
invites,
knowledge,
organizations,
projects,
sandbox,
workforce,
)
from app.interfaces.realtime.router import router as realtime_router
from app.main import lifespan # shared lifespan installs the realtime bus
#: Module-level singleton so the RPC proxy and the tests share one app instance.
_rpc_app: FastAPI | None = None
def build_rpc_app() -> FastAPI:
"""Construct the full handler app. Called once per process."""
application = FastAPI(
title=settings.app_name,
version="0.1.0",
description="AI Workforce Platform — internal RPC dispatch surface.",
lifespan=lifespan,
# This app is reached in-process, never over the network, so the
# interactive docs would only be a liability.
docs_url=None,
openapi_url=None,
)
application.add_middleware(RequestContextMiddleware)
register_exception_handlers(application)
# Same mounting as the old public app: health at the root, everything else
# under /api, and the live socket alongside the RPC surface.
application.include_router(health.router)
application.include_router(auth.router, prefix="/api")
application.include_router(invites.router, prefix="/api")
application.include_router(organizations.router, prefix="/api")
application.include_router(projects.router, prefix="/api")
application.include_router(workforce.router, prefix="/api")
application.include_router(billing.router, prefix="/api")
application.include_router(knowledge.router, prefix="/api")
application.include_router(sandbox.router, prefix="/api")
application.include_router(admin.router, prefix="/api")
application.include_router(agy.router, prefix="/api")
application.include_router(realtime_router, prefix="/api")
return application
def get_rpc_app() -> FastAPI:
"""Return the process-wide internal app, building it on first use."""
global _rpc_app
if _rpc_app is None:
_rpc_app = build_rpc_app()
return _rpc_app