- users.is_platform_owner → users.platform_role (owner|manager|user) با مهاجرت d4e5f6a7b8c9 و backfill. ایندکس یکتای جزئی uq_single_platform_owner تضمین میکند دقیقاً یک مالک وجود داشته باشد — در سطح دیتابیس، نه فقط در کد. - ثبتنام عمومی حذف شد (POST /auth/signup). حسابها فقط با POST /admin/users (فقط مالک، با رمز موقت یکبارمصرف) یا با پذیرش لینک دعوت ساخته میشوند. در مسیر دعوت، اگر حساب وجود نداشته باشد خودِ دعوتشده رمز تعیین میکند؛ اگر وجود داشته باشد لینک هرگز جای reset رمز را نمیگیرد. - ساخت سازمان فقط برای مالک و مدیران؛ کاربر عادی با دعوت عضو میشود. - تنزل یا حذف مدیر، سازمانهایش را به مالک سامانه منتقل میکند (مالکیت + عضویت، با audit). حساب مالک از API تغییرناپذیر است؛ جابهجایی صندلی فقط با POST /admin/transfer-ownership و در یک تراکنش انجام میشود. - افزودن POST /auth/change-password برای تبدیل رمز موقت به رمز خصوصی. - فرانت: صفحهٔ /admin/users، صفحهٔ /account، بازنویسی /accept-invite، حذف /signup، گارد /onboarding برای کاربر عادی، و نشان نقش پلتفرم در پوستهٔ اپ. - PRAGMA foreign_keys=ON برای SQLite تا ON DELETE CASCADE در dev و prod یکسان رفتار کند (پیشتر حذف کاربر، عضویتهایش را یتیم میگذاشت). - تست: ۳۰۰ تست بکاند (شامل tests/test_platform_hierarchy.py با ۲۲ تست) و ۶۴ تست فرانتاند؛ ruff، mypy، tsc، eslint و build تمیز.
84 lines
3.2 KiB
Python
84 lines
3.2 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,
|
|
admin_users,
|
|
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(admin_users.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
|