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
pull/439/head
zijiren 1 month ago committed by GitHub
parent b946d397dd
commit c7d885c4d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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) ![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 ## Documentation
Read the complete documentation at [docs.syncs.tv](https://docs.syncs.tv). Read the complete documentation at [docs.syncs.tv](https://docs.syncs.tv).

@ -44,49 +44,6 @@ SyncTV 是使用 Rust 实现的实时同步观影平台,支持媒体 Provider
![SyncTV 贡献者](https://contrib.nn.ci/api?repo=synctv-org/synctv&repo=synctv-org/synctv-app) ![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)。 完整文档见 [docs.syncs.tv](https://docs.syncs.tv)。

@ -66,9 +66,9 @@ fn serve_path(path: &str, html_navigation: bool, headers: &HeaderMap) -> Respons
) )
.into_response(); .into_response();
} }
find_asset(path) find_asset(path).map_or_else(not_found, |asset| {
.map(|asset| asset_response(asset, html_navigation, headers)) asset_response(asset, html_navigation, headers)
.unwrap_or_else(not_found) })
} }
fn find_asset(path: &str) -> Option<&'static Asset> { fn find_asset(path: &str) -> Option<&'static Asset> {
@ -85,9 +85,8 @@ fn asset_response(asset: &'static Asset, html_navigation: bool, headers: &Header
} else { } else {
"public, max-age=0, must-revalidate" "public, max-age=0, must-revalidate"
}; };
let representation = match select_representation(asset, headers) { let Some(representation) = select_representation(asset, headers) else {
Some(representation) => representation, return StatusCode::NOT_ACCEPTABLE.into_response();
None => return StatusCode::NOT_ACCEPTABLE.into_response(),
}; };
if if_none_match_matches(headers, representation.etag) { if if_none_match_matches(headers, representation.etag) {
let mut response = StatusCode::NOT_MODIFIED.into_response(); let mut response = StatusCode::NOT_MODIFIED.into_response();
@ -404,6 +403,27 @@ mod tests {
assert!(!update_metadata_asset("main.dart.js")); 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] #[tokio::test]
async fn oauth_callback_uses_the_spa_entrypoint() { async fn oauth_callback_uses_the_spa_entrypoint() {
let response = fallback( let response = fallback(

Loading…
Cancel
Save