cjovs commited on
Commit
714de05
·
verified ·
1 Parent(s): e2f927d

Update template rendering for current Starlette signature

Browse files
Files changed (1) hide show
  1. src/web/app.py +33 -64
src/web/app.py CHANGED
@@ -1,21 +1,18 @@
1
- """
2
- FastAPI 应用主文件
3
- 轻量级 Web UI,支持注册、账号管理、设置
4
- """
5
 
 
 
6
  import logging
7
- import sys
8
  import secrets
9
- import hmac
10
- import hashlib
11
- from typing import Optional
12
  from pathlib import Path
 
13
 
14
- from fastapi import FastAPI, Request, Form
15
- from fastapi.staticfiles import StaticFiles
16
- from fastapi.templating import Jinja2Templates
17
  from fastapi.middleware.cors import CORSMiddleware
18
  from fastapi.responses import HTMLResponse, RedirectResponse
 
 
19
 
20
  from ..config.settings import get_settings
21
  from .routes import api_router
@@ -24,20 +21,16 @@ from .task_manager import task_manager
24
 
25
  logger = logging.getLogger(__name__)
26
 
27
- # 获取项目根目录
28
- # PyInstaller 打包后静态资源在 sys._MEIPASS,开发时在源码根目录
29
- if getattr(sys, 'frozen', False):
30
- _RESOURCE_ROOT = Path(sys._MEIPASS)
31
  else:
32
- _RESOURCE_ROOT = Path(__file__).parent.parent.parent
33
 
34
- # 静态文件和模板目录
35
- STATIC_DIR = _RESOURCE_ROOT / "static"
36
- TEMPLATES_DIR = _RESOURCE_ROOT / "templates"
37
 
38
 
39
  def _build_static_asset_version(static_dir: Path) -> str:
40
- """基于静态文件最后修改时间生成版本号,避免部署后浏览器继续使用旧缓存。"""
41
  latest_mtime = 0
42
  if static_dir.exists():
43
  for path in static_dir.rglob("*"):
@@ -47,7 +40,6 @@ def _build_static_asset_version(static_dir: Path) -> str:
47
 
48
 
49
  def create_app() -> FastAPI:
50
- """创建 FastAPI 应用实例"""
51
  settings = get_settings()
52
 
53
  app = FastAPI(
@@ -58,7 +50,6 @@ def create_app() -> FastAPI:
58
  redoc_url="/api/redoc" if settings.debug else None,
59
  )
60
 
61
- # CORS 中间件
62
  app.add_middleware(
63
  CORSMiddleware,
64
  allow_origins=["*"],
@@ -67,28 +58,18 @@ def create_app() -> FastAPI:
67
  allow_headers=["*"],
68
  )
69
 
70
- # 挂载静态文件
71
- if STATIC_DIR.exists():
72
- app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
73
- logger.info(f"静态文件目录: {STATIC_DIR}")
74
- else:
75
- # 创建静态目录
76
  STATIC_DIR.mkdir(parents=True, exist_ok=True)
77
- app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
78
- logger.info(f"创建静态文件目录: {STATIC_DIR}")
79
 
80
- # 创建模板目录
81
  if not TEMPLATES_DIR.exists():
82
  TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
83
- logger.info(f"创建模板目录: {TEMPLATES_DIR}")
84
 
85
- # 注册 API 路由
86
  app.include_router(api_router, prefix="/api")
87
-
88
- # 注册 WebSocket 路由
89
  app.include_router(ws_router, prefix="/api")
90
 
91
- # 模板引擎
92
  templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
93
  templates.env.globals["static_version"] = _build_static_asset_version(STATIC_DIR)
94
 
@@ -106,21 +87,21 @@ def create_app() -> FastAPI:
106
 
107
  @app.get("/login", response_class=HTMLResponse)
108
  async def login_page(request: Request, next: Optional[str] = "/"):
109
- """登录页面"""
110
  return templates.TemplateResponse(
 
111
  "login.html",
112
- {"request": request, "error": "", "next": next or "/"}
113
  )
114
 
115
  @app.post("/login")
116
  async def login_submit(request: Request, password: str = Form(...), next: Optional[str] = "/"):
117
- """处理登录提交"""
118
  expected = get_settings().webui_access_password.get_secret_value()
119
  if not secrets.compare_digest(password, expected):
120
  return templates.TemplateResponse(
 
121
  "login.html",
122
  {"request": request, "error": "密码错误", "next": next or "/"},
123
- status_code=401
124
  )
125
 
126
  response = RedirectResponse(url=next or "/", status_code=302)
@@ -129,73 +110,61 @@ def create_app() -> FastAPI:
129
 
130
  @app.get("/logout")
131
  async def logout(request: Request, next: Optional[str] = "/login"):
132
- """退出登录"""
133
  response = RedirectResponse(url=next or "/login", status_code=302)
134
  response.delete_cookie("webui_auth")
135
  return response
136
 
137
  @app.get("/", response_class=HTMLResponse)
138
  async def index(request: Request):
139
- """首页 - 注册页面"""
140
  if not _is_authenticated(request):
141
  return _redirect_to_login(request)
142
- return templates.TemplateResponse("index.html", {"request": request})
143
 
144
  @app.get("/accounts", response_class=HTMLResponse)
145
  async def accounts_page(request: Request):
146
- """账号管理页面"""
147
  if not _is_authenticated(request):
148
  return _redirect_to_login(request)
149
- return templates.TemplateResponse("accounts.html", {"request": request})
150
 
151
  @app.get("/email-services", response_class=HTMLResponse)
152
  async def email_services_page(request: Request):
153
- """邮箱服务管理页面"""
154
  if not _is_authenticated(request):
155
  return _redirect_to_login(request)
156
- return templates.TemplateResponse("email_services.html", {"request": request})
157
 
158
  @app.get("/settings", response_class=HTMLResponse)
159
  async def settings_page(request: Request):
160
- """设置页面"""
161
  if not _is_authenticated(request):
162
  return _redirect_to_login(request)
163
- return templates.TemplateResponse("settings.html", {"request": request})
164
 
165
  @app.get("/payment", response_class=HTMLResponse)
166
  async def payment_page(request: Request):
167
- """支付页面"""
168
- return templates.TemplateResponse("payment.html", {"request": request})
169
 
170
  @app.on_event("startup")
171
  async def startup_event():
172
- """应用启动事件"""
173
  import asyncio
174
  from ..database.init_db import initialize_database
175
 
176
- # 确保数据库已初始化(reload 模式下子进程也需要初始化)
177
  try:
178
  initialize_database()
179
- except Exception as e:
180
- logger.warning(f"数据库初始化: {e}")
181
 
182
- # 设置 TaskManager 的事件循环
183
- loop = asyncio.get_event_loop()
184
- task_manager.set_loop(loop)
185
 
186
  logger.info("=" * 50)
187
- logger.info(f"{settings.app_name} v{settings.app_version} 启动中,程序正在伸懒腰...")
188
- logger.info(f"调试模式: {settings.debug}")
189
- logger.info(f"数据库连接已接好线: {settings.database_url}")
190
  logger.info("=" * 50)
191
 
192
  @app.on_event("shutdown")
193
  async def shutdown_event():
194
- """应用关闭事件"""
195
- logger.info("应用关闭,今天先收摊啦")
196
 
197
  return app
198
 
199
 
200
- # 创建全局应用实例
201
  app = create_app()
 
1
+ """FastAPI application entrypoint for the web UI."""
 
 
 
2
 
3
+ import hashlib
4
+ import hmac
5
  import logging
 
6
  import secrets
7
+ import sys
 
 
8
  from pathlib import Path
9
+ from typing import Optional
10
 
11
+ from fastapi import FastAPI, Form, Request
 
 
12
  from fastapi.middleware.cors import CORSMiddleware
13
  from fastapi.responses import HTMLResponse, RedirectResponse
14
+ from fastapi.staticfiles import StaticFiles
15
+ from fastapi.templating import Jinja2Templates
16
 
17
  from ..config.settings import get_settings
18
  from .routes import api_router
 
21
 
22
  logger = logging.getLogger(__name__)
23
 
24
+ if getattr(sys, "frozen", False):
25
+ resource_root = Path(sys._MEIPASS)
 
 
26
  else:
27
+ resource_root = Path(__file__).parent.parent.parent
28
 
29
+ STATIC_DIR = resource_root / "static"
30
+ TEMPLATES_DIR = resource_root / "templates"
 
31
 
32
 
33
  def _build_static_asset_version(static_dir: Path) -> str:
 
34
  latest_mtime = 0
35
  if static_dir.exists():
36
  for path in static_dir.rglob("*"):
 
40
 
41
 
42
  def create_app() -> FastAPI:
 
43
  settings = get_settings()
44
 
45
  app = FastAPI(
 
50
  redoc_url="/api/redoc" if settings.debug else None,
51
  )
52
 
 
53
  app.add_middleware(
54
  CORSMiddleware,
55
  allow_origins=["*"],
 
58
  allow_headers=["*"],
59
  )
60
 
61
+ if not STATIC_DIR.exists():
 
 
 
 
 
62
  STATIC_DIR.mkdir(parents=True, exist_ok=True)
63
+ logger.info("Created static directory: %s", STATIC_DIR)
64
+ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
65
 
 
66
  if not TEMPLATES_DIR.exists():
67
  TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
68
+ logger.info("Created templates directory: %s", TEMPLATES_DIR)
69
 
 
70
  app.include_router(api_router, prefix="/api")
 
 
71
  app.include_router(ws_router, prefix="/api")
72
 
 
73
  templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
74
  templates.env.globals["static_version"] = _build_static_asset_version(STATIC_DIR)
75
 
 
87
 
88
  @app.get("/login", response_class=HTMLResponse)
89
  async def login_page(request: Request, next: Optional[str] = "/"):
 
90
  return templates.TemplateResponse(
91
+ request,
92
  "login.html",
93
+ {"request": request, "error": "", "next": next or "/"},
94
  )
95
 
96
  @app.post("/login")
97
  async def login_submit(request: Request, password: str = Form(...), next: Optional[str] = "/"):
 
98
  expected = get_settings().webui_access_password.get_secret_value()
99
  if not secrets.compare_digest(password, expected):
100
  return templates.TemplateResponse(
101
+ request,
102
  "login.html",
103
  {"request": request, "error": "密码错误", "next": next or "/"},
104
+ status_code=401,
105
  )
106
 
107
  response = RedirectResponse(url=next or "/", status_code=302)
 
110
 
111
  @app.get("/logout")
112
  async def logout(request: Request, next: Optional[str] = "/login"):
 
113
  response = RedirectResponse(url=next or "/login", status_code=302)
114
  response.delete_cookie("webui_auth")
115
  return response
116
 
117
  @app.get("/", response_class=HTMLResponse)
118
  async def index(request: Request):
 
119
  if not _is_authenticated(request):
120
  return _redirect_to_login(request)
121
+ return templates.TemplateResponse(request, "index.html", {"request": request})
122
 
123
  @app.get("/accounts", response_class=HTMLResponse)
124
  async def accounts_page(request: Request):
 
125
  if not _is_authenticated(request):
126
  return _redirect_to_login(request)
127
+ return templates.TemplateResponse(request, "accounts.html", {"request": request})
128
 
129
  @app.get("/email-services", response_class=HTMLResponse)
130
  async def email_services_page(request: Request):
 
131
  if not _is_authenticated(request):
132
  return _redirect_to_login(request)
133
+ return templates.TemplateResponse(request, "email_services.html", {"request": request})
134
 
135
  @app.get("/settings", response_class=HTMLResponse)
136
  async def settings_page(request: Request):
 
137
  if not _is_authenticated(request):
138
  return _redirect_to_login(request)
139
+ return templates.TemplateResponse(request, "settings.html", {"request": request})
140
 
141
  @app.get("/payment", response_class=HTMLResponse)
142
  async def payment_page(request: Request):
143
+ return templates.TemplateResponse(request, "payment.html", {"request": request})
 
144
 
145
  @app.on_event("startup")
146
  async def startup_event():
 
147
  import asyncio
148
  from ..database.init_db import initialize_database
149
 
 
150
  try:
151
  initialize_database()
152
+ except Exception as exc:
153
+ logger.warning("Database initialization during startup raised: %s", exc)
154
 
155
+ task_manager.set_loop(asyncio.get_running_loop())
 
 
156
 
157
  logger.info("=" * 50)
158
+ logger.info("%s v%s starting", settings.app_name, settings.app_version)
159
+ logger.info("Debug mode: %s", settings.debug)
160
+ logger.info("Configured database URL: %s", settings.database_url)
161
  logger.info("=" * 50)
162
 
163
  @app.on_event("shutdown")
164
  async def shutdown_event():
165
+ logger.info("Application shutdown")
 
166
 
167
  return app
168
 
169
 
 
170
  app = create_app()