From 8ebec90258a3ff508a048be28bd07ee286c8e77b Mon Sep 17 00:00:00 2001 From: zijiren233 Date: Sun, 6 Sep 2026 00:15:55 +0800 Subject: [PATCH] chore: simplify --- synctv-api-common/src/impls/client/convert.rs | 6 --- .../src/impls/client/playback.rs | 1 - synctv-api-common/src/impls/client/stream.rs | 44 ++++------------- .../src/impls/messaging/resource_observer.rs | 1 - synctv-api-http/src/http/notifications.rs | 6 +-- synctv-api-http/src/http/room/playback.rs | 1 - synctv-api-http/src/http/room/streams.rs | 5 +- synctv-api-http/src/http/tests.rs | 6 ++- synctv-api-http/src/openapi.rs | 20 +++++--- synctv-core/src/service/publish_key.rs | 4 +- synctv-core/src/service/publish_key/tests.rs | 49 ------------------- synctv-proto/proto/client.proto | 9 +--- synctv-web-ui/README.md | 1 - synctv-web-ui/build.rs | 19 +------ synctv-web-ui/src/build_support.rs | 20 +------- 15 files changed, 38 insertions(+), 154 deletions(-) diff --git a/synctv-api-common/src/impls/client/convert.rs b/synctv-api-common/src/impls/client/convert.rs index 376b6757..fcc1ee02 100644 --- a/synctv-api-common/src/impls/client/convert.rs +++ b/synctv-api-common/src/impls/client/convert.rs @@ -3341,12 +3341,6 @@ pub fn playback_history_page_to_proto( .transpose() .map_err(|error| proto_encode_error("playback history entry", &error))? .unwrap_or_default(), - next_before_entry_id: page - .next_cursor_entry_id - .map(|id| public_id_codec.encode_playback_history_entry_id(id)) - .transpose() - .map_err(|error| proto_encode_error("playback history entry", &error))? - .unwrap_or_default(), next_cursor_entry_id: page .next_cursor_entry_id .map(|id| public_id_codec.encode_playback_history_entry_id(id)) diff --git a/synctv-api-common/src/impls/client/playback.rs b/synctv-api-common/src/impls/client/playback.rs index f5e68ac3..aba18f82 100644 --- a/synctv-api-common/src/impls/client/playback.rs +++ b/synctv-api-common/src/impls/client/playback.rs @@ -1072,7 +1072,6 @@ impl ClientApiImpl { let cursor_entry_id = req .cursor_entry_id .as_deref() - .or(req.before_entry_id.as_deref()) .map(|id| self.public_id_codec.decode_playback_history_entry_id(id)) .transpose() .map_err(|_| { diff --git a/synctv-api-common/src/impls/client/stream.rs b/synctv-api-common/src/impls/client/stream.rs index b11e0404..57ac3c87 100644 --- a/synctv-api-common/src/impls/client/stream.rs +++ b/synctv-api-common/src/impls/client/stream.rs @@ -208,25 +208,24 @@ async fn filter_usable_stream_media_ids( pub(crate) fn publish_key_options( req: &CreateRoomPublishKeyRequest, -) -> Result, ApiError> { +) -> Result { let key_type = match PublishKeyType::try_from(req.r#type) .map_err(|_| ApiError::InvalidInput("publish key type is invalid".to_string()))? { PublishKeyType::SingleUse => CorePublishKeyType::SingleUse, PublishKeyType::Expiring => CorePublishKeyType::Expiring, PublishKeyType::Permanent => CorePublishKeyType::Permanent, - PublishKeyType::Unspecified if req.expires_at.is_none() => return Ok(None), PublishKeyType::Unspecified => { return Err(ApiError::InvalidInput( - "publish key type is required when expiration is provided".to_string(), + "publish key type must be specified".to_string(), )); } }; - Ok(Some(PublishKeyOptions { + Ok(PublishKeyOptions { key_type, expires_at: req.expires_at, - })) + }) } pub(crate) struct RoomPublishKeyIssuer<'a> { @@ -256,21 +255,12 @@ impl<'a> RoomPublishKeyIssuer<'a> { room_id: RoomId, media_id: MediaId, actor_user_id: &UserId, - options: Option, + options: PublishKeyOptions, ) -> Result { - let publish_key = match options { - Some(options) => self.publish_key_service.generate_publish_key_with_options( - &room_id, - &media_id, - actor_user_id, - options, - ), - None => { - self.publish_key_service - .generate_publish_key(&room_id, &media_id, actor_user_id) - } - } - .map_err(|error| ApiError::InvalidInput(error.to_string()))?; + let publish_key = self + .publish_key_service + .generate_publish_key_with_options(&room_id, &media_id, actor_user_id, options) + .map_err(|error| ApiError::InvalidInput(error.to_string()))?; let room_id = self .public_id_codec .encode_room_id(room_id) @@ -682,24 +672,10 @@ mod tests { } #[test] - fn publish_key_options_preserves_legacy_default() -> TestResult { - let options = api_ok(publish_key_options( - &synctv_proto::client::CreateRoomPublishKeyRequest { - media_id: "med_AbC123".to_string(), - ..Default::default() - }, - ))?; - - assert!(options.is_none()); - Ok(()) - } - - #[test] - fn publish_key_options_rejects_ambiguous_legacy_expiration() -> TestResult { + fn publish_key_options_requires_type() -> TestResult { let error = api_err(publish_key_options( &synctv_proto::client::CreateRoomPublishKeyRequest { media_id: "med_AbC123".to_string(), - expires_at: Some(1_800_000_000), ..Default::default() }, ))?; diff --git a/synctv-api-common/src/impls/messaging/resource_observer.rs b/synctv-api-common/src/impls/messaging/resource_observer.rs index ced72f12..f1748d77 100644 --- a/synctv-api-common/src/impls/messaging/resource_observer.rs +++ b/synctv-api-common/src/impls/messaging/resource_observer.rs @@ -3417,7 +3417,6 @@ impl ResourceObserver { let cursor_entry_id = request .cursor_entry_id .as_deref() - .or(request.before_entry_id.as_deref()) .map(|id| self.public_id_codec.decode_playback_history_entry_id(id)) .transpose() .map_err(|_| "Invalid playback history cursor_entry_id".to_string())?; diff --git a/synctv-api-http/src/http/notifications.rs b/synctv-api-http/src/http/notifications.rs index 2d218167..3118b4ba 100644 --- a/synctv-api-http/src/http/notifications.rs +++ b/synctv-api-http/src/http/notifications.rs @@ -182,7 +182,7 @@ pub async fn mark_as_read( post, path = "/api/notifications/read-all", tag = "Notification", - request_body = Option, + request_body = MarkAllAsReadRequest, responses( (status = 204, description = "Notifications marked as read"), (status = 400, description = "Invalid timestamp", body = crate::openapi::GoogleRpcStatusSchema), @@ -197,15 +197,13 @@ pub async fn mark_as_read( pub async fn mark_all_as_read( request_meta: RequestMetadata, State(state): State, - req: Option>, + Json(req): Json, ) -> AppResult { let api = get_notification_api(&state)?; let request_meta = request_meta .0 .with_timeout(Some(synctv_core::resilience::timeout::HTTP_REQUEST_TIMEOUT)); - let req = req.map_or_else(MarkAllAsReadRequest::default, |Json(req)| req); - state .shared_api_runtime .client_api diff --git a/synctv-api-http/src/http/room/playback.rs b/synctv-api-http/src/http/room/playback.rs index f07807bf..bd79af0d 100644 --- a/synctv-api-http/src/http/room/playback.rs +++ b/synctv-api-http/src/http/room/playback.rs @@ -185,7 +185,6 @@ pub async fn play_previous( tag = "Room", params( ("roomId" = String, Path, description = "Room ID"), - ("beforeEntryId" = Option, Query, description = "Legacy newest-first pagination cursor"), ("cursorEntryId" = Option, Query, description = "Pagination cursor for the selected sort direction"), ("limit" = Option, Query, description = "Page size, up to 100"), ("sortDirection" = Option, Query, description = "Sort direction enum value; defaults to descending") diff --git a/synctv-api-http/src/http/room/streams.rs b/synctv-api-http/src/http/room/streams.rs index 541bf61c..22be2cd5 100644 --- a/synctv-api-http/src/http/room/streams.rs +++ b/synctv-api-http/src/http/room/streams.rs @@ -64,7 +64,7 @@ pub async fn list_room_streams( post, path = "/api/playback-providers/{roomId}/rtmp/{mediaId}/publish-key", tag = "RTMP Playback Provider", - request_body = Option, + request_body = CreateRoomPublishKeyRequest, params( ("roomId" = String, Path, description = "Room ID"), ("mediaId" = String, Path, description = "Media ID") @@ -85,10 +85,9 @@ pub async fn create_room_publish_key( request_meta: RequestMetadata, State(state): State, Path(path): Path, - req: Option>, + Json(mut req): Json, ) -> AppResult> { let room_id = path.room_id; - let mut req = req.map(|Json(req)| req).unwrap_or_default(); req.media_id = path.media_id; let response = execute_user_endpoint( &state, diff --git a/synctv-api-http/src/http/tests.rs b/synctv-api-http/src/http/tests.rs index 5b55ce44..ec392f16 100644 --- a/synctv-api-http/src/http/tests.rs +++ b/synctv-api-http/src/http/tests.rs @@ -3660,7 +3660,8 @@ async fn test_room_stream_and_live_playback_provider_routes_are_reachable_under_ Request::builder() .method("POST") .uri("/api/playback-providers/room_AbC123xYz890/rtmp/med_ZyX098wVu765/publish-key") - .body(Body::empty()), + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{"type":1,"expiresAt":1800000000}"#)), )?; let response = test_response(app.clone().oneshot(request).await)?; assert_eq!(response.status(), StatusCode::UNAUTHORIZED); @@ -3792,7 +3793,8 @@ async fn test_notification_routes_fail_closed_when_service_missing() -> TestResu Request::builder() .method("POST") .uri("/api/notifications/read-all") - .body(Body::empty()), + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(Body::from(r#"{}"#)), )?; let write_response = test_response(app.oneshot(write_request).await)?; assert_eq!(write_response.status(), StatusCode::SERVICE_UNAVAILABLE); diff --git a/synctv-api-http/src/openapi.rs b/synctv-api-http/src/openapi.rs index 2daff663..8b293f01 100644 --- a/synctv-api-http/src/openapi.rs +++ b/synctv-api-http/src/openapi.rs @@ -1976,7 +1976,7 @@ mod tests { } #[test] - fn openapi_marks_notifications_read_all_body_optional() -> TestResult { + fn openapi_marks_notifications_read_all_body_required() -> TestResult { let doc = openapi_json()?; let request_body = &doc["paths"]["/api/notifications/read-all"]["post"]["requestBody"]; @@ -1987,16 +1987,16 @@ mod tests { let required = request_body["required"].as_bool(); - assert_ne!( + assert_eq!( required, Some(true), - "mark-all-as-read should not document its request body as required" + "mark-all-as-read should document its request body as required" ); Ok(()) } #[test] - fn openapi_marks_publish_key_body_optional() -> TestResult { + fn openapi_marks_publish_key_body_required() -> TestResult { let doc = openapi_json()?; let request_body = &doc["paths"] @@ -2005,10 +2005,10 @@ mod tests { request_body.is_object(), "publish-key creation should document its request body schema" ); - assert_ne!( + assert_eq!( request_body["required"].as_bool(), Some(true), - "publish-key creation may omit the request body" + "publish-key creation should require its request body" ); Ok(()) } @@ -2268,7 +2268,7 @@ mod tests { "streamPreference", "query", )?; - for name in ["beforeEntryId", "cursorEntryId", "limit", "sortDirection"] { + for name in ["cursorEntryId", "limit", "sortDirection"] { assert_parameter_location( &doc, "/api/rooms/{roomId}/playback/history", @@ -2277,6 +2277,12 @@ mod tests { "query", )?; } + assert_parameter_absent( + &doc, + "/api/rooms/{roomId}/playback/history", + "get", + "beforeEntryId", + )?; Ok(()) } diff --git a/synctv-core/src/service/publish_key.rs b/synctv-core/src/service/publish_key.rs index 8f9e92a0..b13e46e8 100644 --- a/synctv-core/src/service/publish_key.rs +++ b/synctv-core/src/service/publish_key.rs @@ -38,10 +38,9 @@ pub struct PublishKey { pub key_type: PublishKeyType, } -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum PublishKeyType { - #[default] SingleUse, Expiring, Permanent, @@ -101,7 +100,6 @@ pub struct PublishClaims { /// JWT ID (unique token identifier) pub jti: String, /// Key lifecycle type - #[serde(default)] pub key_type: PublishKeyType, } diff --git a/synctv-core/src/service/publish_key/tests.rs b/synctv-core/src/service/publish_key/tests.rs index 6ec9de7b..65dcd1b6 100644 --- a/synctv-core/src/service/publish_key/tests.rs +++ b/synctv-core/src/service/publish_key/tests.rs @@ -304,55 +304,6 @@ async fn explicit_publish_key_types_enforce_their_lifecycle() { assert!(service.validate_publish_key(&permanent.token).await.is_ok()); } -#[tokio::test] -async fn legacy_publish_keys_without_key_type_default_to_single_use() { - #[derive(Serialize)] - struct LegacyPublishClaims { - room_id: String, - media_id: String, - user_id: String, - perm_manage_live_streams: bool, - iat: i64, - exp: i64, - jti: String, - } - - let now = 1_700_000_000i64; - let clock = fixed_clock(now * 1000); - let jwt = create_jwt_service_with_clock(clock.clone()); - let service = ok( - PublishKeyService::new(jwt.clone(), clock, 24), - "publish key service should build", - ); - let room_id = RoomId::new(); - let media_id = MediaId::new(); - let user_id = UserId::new(); - let token = ok( - jwt.sign_custom(&LegacyPublishClaims { - room_id: room_id.to_string(), - media_id: media_id.to_string(), - user_id: user_id.to_string(), - perm_manage_live_streams: true, - iat: now, - exp: now + 3600, - jti: "legacy-publish-key".to_string(), - }), - "legacy publish key should sign", - ); - - let claims = ok( - service - .validate_publish_key_for_stream_claims(&token, &room_id, &media_id) - .await, - "legacy publish key should remain valid", - ); - assert_eq!(claims.key_type, PublishKeyType::SingleUse); - assert!(service - .validate_publish_key_for_stream_claims(&token, &room_id, &media_id) - .await - .is_err()); -} - #[test] fn publish_key_options_reject_inconsistent_expiration() { let now = 1_700_000_000i64; diff --git a/synctv-proto/proto/client.proto b/synctv-proto/proto/client.proto index 3e203fb0..7b264474 100644 --- a/synctv-proto/proto/client.proto +++ b/synctv-proto/proto/client.proto @@ -2211,12 +2211,7 @@ message PlaybackHistoryEntry { } message ListPlaybackHistoryRequest { - option (buf.validate.message).cel = { - id: "list_playback_history.single_cursor" - message: "before_entry_id and cursor_entry_id cannot both be set" - expression: "!(has(this.before_entry_id) && has(this.cursor_entry_id))" - }; - optional string before_entry_id = 1 [(buf.validate.field).string = {min_len: 4 max_len: 64 pattern: "^ph_[A-Za-z0-9]+$"}]; + reserved 1; int32 limit = 2 [(buf.validate.field).int32 = {gte: 0 lte: 100}]; optional string cursor_entry_id = 3 [(buf.validate.field).string = {min_len: 4 max_len: 64 pattern: "^ph_[A-Za-z0-9]+$"}]; SortDirection sort_direction = 4 [(buf.validate.field).enum.defined_only = true]; @@ -2225,7 +2220,7 @@ message ListPlaybackHistoryRequest { message ListPlaybackHistoryResponse { repeated PlaybackHistoryEntry entries = 1; string history_cursor_id = 2; - string next_before_entry_id = 3; + reserved 3; string next_cursor_entry_id = 4; } diff --git a/synctv-web-ui/README.md b/synctv-web-ui/README.md index 1fb5bcbb..3fb6f00f 100644 --- a/synctv-web-ui/README.md +++ b/synctv-web-ui/README.md @@ -97,7 +97,6 @@ embeds the archive; it never installs Flutter or builds the frontend. | Variable | Behavior | | --- | --- | | `SYNCTV_WEB_CONFIG` | Select a configuration file explicitly. | -| `SYNCTV_WEB_DIST` | Use a prebuilt directory. This compatibility override takes precedence over configured sources. | | `SYNCTV_WEB_CACHE_DIR` | Select the Git, Flutter output, and compression cache root. | | `SYNCTV_WEB_EXPORT_DIR` | Copy the final uncompressed distribution to a disjoint directory. | | `SYNCTV_WEB_OFFLINE` | Disable Git fetches and use `flutter pub get --offline`. Missing cache entries fail. | diff --git a/synctv-web-ui/build.rs b/synctv-web-ui/build.rs index 196fca02..de21695a 100644 --- a/synctv-web-ui/build.rs +++ b/synctv-web-ui/build.rs @@ -17,7 +17,6 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Output}; const CONFIG_ENV: &str = "SYNCTV_WEB_CONFIG"; -const LEGACY_DIST_ENV: &str = "SYNCTV_WEB_DIST"; const CACHE_ENV: &str = "SYNCTV_WEB_CACHE_DIR"; const OFFLINE_ENV: &str = "SYNCTV_WEB_OFFLINE"; const FORCE_ENV: &str = "SYNCTV_WEB_FORCE_REBUILD"; @@ -42,14 +41,7 @@ fn build() -> Result<(), String> { .map_err(|error| format!("failed to write disabled Web UI manifest: {error}"))?; return Ok(()); } - for name in [ - CONFIG_ENV, - LEGACY_DIST_ENV, - CACHE_ENV, - OFFLINE_ENV, - FORCE_ENV, - EXPORT_ENV, - ] { + for name in [CONFIG_ENV, CACHE_ENV, OFFLINE_ENV, FORCE_ENV, EXPORT_ENV] { println!("cargo:rerun-if-env-changed={name}"); } @@ -64,14 +56,7 @@ fn build() -> Result<(), String> { let explicit_config = env::var_os(CONFIG_ENV) .map(PathBuf::from) .map(|path| absolute_control_path(control_base, path)); - let legacy_dist = env::var_os(LEGACY_DIST_ENV) - .map(PathBuf::from) - .map(|path| absolute_control_path(control_base, path)); - let loaded = load_config( - &manifest_dir, - explicit_config.as_deref(), - legacy_dist.as_deref(), - )?; + let loaded = load_config(&manifest_dir, explicit_config.as_deref())?; println!("cargo:rerun-if-changed={}", loaded.path.display()); let cache_dir = env::var_os(CACHE_ENV).map_or_else( diff --git a/synctv-web-ui/src/build_support.rs b/synctv-web-ui/src/build_support.rs index db1021a3..fb58148a 100644 --- a/synctv-web-ui/src/build_support.rs +++ b/synctv-web-ui/src/build_support.rs @@ -74,23 +74,7 @@ pub struct LoadedConfig { pub path: PathBuf, } -pub fn load_config( - manifest_dir: &Path, - explicit: Option<&Path>, - legacy_dist: Option<&Path>, -) -> Result { - if let Some(path) = legacy_dist { - return Ok(LoadedConfig { - config: WebUiConfig { - schema_version: 1, - source: WebUiSource::Dist { - path: path.to_path_buf(), - }, - build: FlutterBuild::default(), - }, - path: manifest_dir.join(LOCAL_CONFIG), - }); - } +pub fn load_config(manifest_dir: &Path, explicit: Option<&Path>) -> Result { let path = explicit.map_or_else( || { let local = manifest_dir.join(LOCAL_CONFIG); @@ -564,7 +548,7 @@ mod tests { "schema-version=1\n[source]\nkind='dist'\npath='local'\n", )?; - let loaded = load_config(directory.path(), None, None)?; + let loaded = load_config(directory.path(), None)?; assert_eq!(loaded.path, directory.path().join(LOCAL_CONFIG)); assert!(matches!(