Knowledge files were only partitioned in the database, which made nested uploads, local folder visibility, and delete behavior diverge from the UI. This change makes folder selection drive physical storage paths, keeps original filenames, adds a minimal WebDAV mount/sync path, and reshapes the knowledge panel so local and remote sources can share the same surface. Constraint: Existing knowledge flow already depends on local-folder-backed uploads and document indexing Rejected: Real-time bidirectional WebDAV sync | too much conflict and lifecycle complexity for the first pass Confidence: medium Scope-risk: moderate Reversibility: messy Directive: Keep remote mounts single-direction into local knowledge folders until etag-based incremental sync and conflict rules are verified Tested: Python py_compile on new/modified backend files; LSP diagnostics on new frontend/backend files; manual targeted code-path inspection Not-tested: Full pytest/vitest end-to-end runs blocked by environment temp/cache permission errors; live WebDAV server interoperability
146 lines
4.1 KiB
Python
146 lines
4.1 KiB
Python
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
from app.database import init_db, async_session
|
|
import app.models # noqa: F401 - 注册所有模型
|
|
from app.routers import (
|
|
auth_router,
|
|
conversation_router,
|
|
document_router,
|
|
task_router,
|
|
forum_router,
|
|
graph_router,
|
|
agent_router,
|
|
todo_router,
|
|
reminder_router,
|
|
goal_router,
|
|
schedule_center_router,
|
|
settings_router,
|
|
folder_router,
|
|
skill_router,
|
|
log_router,
|
|
system_router,
|
|
brain_router,
|
|
hooks_router,
|
|
plugins_router,
|
|
marketplace_router,
|
|
agent_skills_router,
|
|
agent_sessions_router,
|
|
terminal_router,
|
|
tools_router,
|
|
remote_mount_router,
|
|
)
|
|
from app.routers.scheduler import router as scheduler_router
|
|
from app.services.scheduler_service import start_scheduler, stop_scheduler, get_scheduler_status
|
|
from app.services.admin_bootstrap_service import ensure_admin_user, ensure_builtin_skills
|
|
from app.config import settings
|
|
from app.logging_utils import (
|
|
setup_logging,
|
|
request_logging_middleware,
|
|
log_http_exception,
|
|
log_validation_exception,
|
|
log_unhandled_exception,
|
|
persist_system_log,
|
|
)
|
|
import os
|
|
|
|
|
|
INSECURE_SECRET_KEYS = {
|
|
"change-me-in-production",
|
|
"change-me-to-a-random-secret-key",
|
|
"jarvis-secret-key-change-in-production",
|
|
}
|
|
|
|
|
|
def validate_startup_security() -> None:
|
|
if not settings.DEBUG and settings.SECRET_KEY in INSECURE_SECRET_KEYS:
|
|
raise RuntimeError("SECRET_KEY must be changed before running with DEBUG disabled")
|
|
|
|
|
|
async def run_startup() -> None:
|
|
validate_startup_security()
|
|
await init_db()
|
|
async with async_session() as session:
|
|
await ensure_admin_user(session, settings)
|
|
await ensure_builtin_skills(session)
|
|
await persist_system_log(
|
|
message="application_started",
|
|
source="app",
|
|
operation="app.startup",
|
|
details={"version": settings.APP_VERSION},
|
|
)
|
|
start_scheduler()
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# 启动
|
|
setup_logging(settings.DEBUG)
|
|
os.makedirs(settings.DATA_DIR, exist_ok=True)
|
|
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
|
|
os.makedirs(settings.CHROMA_PERSIST_DIR, exist_ok=True)
|
|
await run_startup()
|
|
yield
|
|
# 关闭
|
|
stop_scheduler()
|
|
|
|
|
|
app = FastAPI(
|
|
title=settings.APP_NAME,
|
|
version=settings.APP_VERSION,
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.CORS_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
app.middleware("http")(request_logging_middleware)
|
|
app.add_exception_handler(StarletteHTTPException, log_http_exception)
|
|
app.add_exception_handler(RequestValidationError, log_validation_exception)
|
|
app.add_exception_handler(Exception, log_unhandled_exception)
|
|
|
|
# 注册路由
|
|
app.include_router(auth_router)
|
|
app.include_router(conversation_router)
|
|
app.include_router(document_router)
|
|
app.include_router(task_router)
|
|
app.include_router(forum_router)
|
|
app.include_router(graph_router)
|
|
app.include_router(agent_router)
|
|
app.include_router(todo_router)
|
|
app.include_router(reminder_router)
|
|
app.include_router(goal_router)
|
|
app.include_router(schedule_center_router)
|
|
app.include_router(settings_router)
|
|
app.include_router(folder_router)
|
|
app.include_router(skill_router)
|
|
app.include_router(log_router)
|
|
app.include_router(system_router)
|
|
app.include_router(brain_router)
|
|
app.include_router(scheduler_router)
|
|
app.include_router(hooks_router)
|
|
app.include_router(plugins_router)
|
|
app.include_router(marketplace_router)
|
|
app.include_router(agent_skills_router)
|
|
app.include_router(agent_sessions_router)
|
|
app.include_router(terminal_router)
|
|
app.include_router(tools_router)
|
|
app.include_router(remote_mount_router)
|
|
|
|
|
|
@app.get("/api/health")
|
|
async def health():
|
|
return {
|
|
"status": "ok",
|
|
"version": settings.APP_VERSION,
|
|
"llm_provider": settings.LLM_PROVIDER,
|
|
"scheduler": get_scheduler_status(),
|
|
}
|