From c7d885c4d8d4042215b21f8d8b3ad2c638a9092d Mon Sep 17 00:00:00 2001 From: zijiren <84728412+zijiren233@users.noreply.github.com> Date: Sun, 23 Aug 2026 21:21:13 +0800 Subject: [PATCH] chore: finalize web UI root coverage and docs (#438) ## Summary - verify that the embedded Web UI serves the SPA entrypoint directly at the root path without a redirect - use idiomatic Option handling in the embedded asset response path - remove outdated setup sections from the English and Chinese project READMEs ## Validation - cargo test -p synctv-api-http --features web-ui root_serves_spa_entrypoint_without_redirect - make clippy-check - cargo fmt --check - git diff --check --- README.md | 65 ------------------------------ README.zh-CN.md | 43 -------------------- synctv-api-http/src/http/web_ui.rs | 32 ++++++++++++--- 3 files changed, 26 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index d4d72030..31b76389 100644 --- a/README.md +++ b/README.md @@ -45,71 +45,6 @@ Join the [SyncTV Telegram discussion](https://t.me/synctv) to talk with users an ![SyncTV contributors](https://contrib.nn.ci/api?repo=synctv-org/synctv&repo=synctv-org/synctv-app) -## Quick Start - -Development environment from a full repository checkout: - -```bash -# Starts PostgreSQL and Redis, then runs SyncTV locally with development settings. -make dev-serve - -# Starts optional media/auth/storage dependencies too. -make dev-stack - -# Runs real CLI/curl provider smoke tests through the Makefile dev startup path. -make dev-smoke -``` - -Production Compose uses generated PostgreSQL, Redis, and application secrets: - -```bash -# Requires Docker Compose and openssl. -make compose-init - -# Edit SYNCTV_BOOTSTRAP_ROOT_PASSWORD in .env.synctv before starting. -make compose-up -``` - -Validate configuration: - -```bash -cargo +nightly run -p synctv --bin synctv -- config validate -``` - -Optional migration preflight. The server also runs embedded SQLx migrations automatically during startup: - -```bash -cargo +nightly run -p synctv --bin synctv -- db migrate -``` - -Start locally: - -```bash -cargo +nightly run -p synctv --bin synctv -- serve -``` - -### Embedded Web client - -The optional `web-ui` feature embeds a Flutter Web distribution in the HTTP -server. The browser client always uses the page origin, so one deployed Web UI -belongs to one SyncTV server. Build the configured Web distribution and server: - -```bash -make web-ui-build -make web-release-build -``` - -[`synctv-web-ui/README.md`](synctv-web-ui/README.md) documents prebuilt, -local-project, and immutable Git sources together with cache and offline -controls. The server provides SPA fallback, content types, ETags, Brotli/gzip -variants, cache policy, and CSP. OAuth uses the normal SPA entry point, while -provider verification uses its dedicated static page. API and media routes -remain outside the application-shell cache. - -Keep the app and server protobuf snapshots aligned. Browser playback sends a -versioned `PlaybackClientProfile`; Providers use it with the configured proxy -policy to select direct or proxy routes before returning playback output. - ## Documentation Read the complete documentation at [docs.syncs.tv](https://docs.syncs.tv). diff --git a/README.zh-CN.md b/README.zh-CN.md index 68eec402..fbf52d7d 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -44,49 +44,6 @@ SyncTV 是使用 Rust 实现的实时同步观影平台,支持媒体 Provider ![SyncTV 贡献者](https://contrib.nn.ci/api?repo=synctv-org/synctv&repo=synctv-org/synctv-app) -## 快速开始 - -开发环境需要完整源码仓库: - -```bash -# 启动 PostgreSQL 和 Redis,然后用开发配置在本机运行 SyncTV。 -make dev-serve - -# 同时启动媒体、认证和对象存储等可选依赖。 -make dev-stack - -# 通过 Makefile dev 启动路径执行真实 CLI/curl provider smoke 测试。 -make dev-smoke -``` - -生产 Compose 使用自动生成的 PostgreSQL、Redis 和应用 secret: - -```bash -# 需要 Docker Compose 和 openssl。 -make compose-init - -# 启动前编辑 .env.synctv 中的 SYNCTV_BOOTSTRAP_ROOT_PASSWORD。 -make compose-up -``` - -校验配置: - -```bash -cargo +nightly run -p synctv --bin synctv -- config validate -``` - -可选 migration 预检。服务启动阶段也会自动执行 embedded SQLx migrations: - -```bash -cargo +nightly run -p synctv --bin synctv -- db migrate -``` - -本地启动: - -```bash -cargo +nightly run -p synctv --bin synctv -- serve -``` - ## 文档 完整文档见 [docs.syncs.tv](https://docs.syncs.tv)。 diff --git a/synctv-api-http/src/http/web_ui.rs b/synctv-api-http/src/http/web_ui.rs index 35caea9b..df49c0d0 100644 --- a/synctv-api-http/src/http/web_ui.rs +++ b/synctv-api-http/src/http/web_ui.rs @@ -66,9 +66,9 @@ fn serve_path(path: &str, html_navigation: bool, headers: &HeaderMap) -> Respons ) .into_response(); } - find_asset(path) - .map(|asset| asset_response(asset, html_navigation, headers)) - .unwrap_or_else(not_found) + find_asset(path).map_or_else(not_found, |asset| { + asset_response(asset, html_navigation, headers) + }) } fn find_asset(path: &str) -> Option<&'static Asset> { @@ -85,9 +85,8 @@ fn asset_response(asset: &'static Asset, html_navigation: bool, headers: &Header } else { "public, max-age=0, must-revalidate" }; - let representation = match select_representation(asset, headers) { - Some(representation) => representation, - None => return StatusCode::NOT_ACCEPTABLE.into_response(), + let Some(representation) = select_representation(asset, headers) else { + return StatusCode::NOT_ACCEPTABLE.into_response(); }; if if_none_match_matches(headers, representation.etag) { let mut response = StatusCode::NOT_MODIFIED.into_response(); @@ -404,6 +403,27 @@ mod tests { assert!(!update_metadata_asset("main.dart.js")); } + #[tokio::test] + async fn root_serves_spa_entrypoint_without_redirect() { + assert!( + WEB_UI_AVAILABLE, + "the web-ui feature must embed a SPA entrypoint" + ); + + let response = index(HeaderMap::new()).await; + + assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response.headers().get(header::CONTENT_TYPE), + Some(&HeaderValue::from_static("text/html; charset=utf-8")) + ); + assert_eq!( + response.headers().get(header::CACHE_CONTROL), + Some(&HeaderValue::from_static("no-cache")) + ); + assert!(response.headers().get(header::LOCATION).is_none()); + } + #[tokio::test] async fn oauth_callback_uses_the_spa_entrypoint() { let response = fallback(