chore: simplify

pull/460/head
zijiren233 3 weeks ago
parent 355b7790b6
commit 8ebec90258
No known key found for this signature in database
GPG Key ID: 534E082AAA9B39DC

@ -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))

@ -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(|_| {

@ -208,25 +208,24 @@ async fn filter_usable_stream_media_ids(
pub(crate) fn publish_key_options(
req: &CreateRoomPublishKeyRequest,
) -> Result<Option<PublishKeyOptions>, ApiError> {
) -> Result<PublishKeyOptions, ApiError> {
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<PublishKeyOptions>,
options: PublishKeyOptions,
) -> Result<CreateRoomPublishKeyResponse, ApiError> {
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()
},
))?;

@ -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())?;

@ -182,7 +182,7 @@ pub async fn mark_as_read(
post,
path = "/api/notifications/read-all",
tag = "Notification",
request_body = Option<MarkAllAsReadRequest>,
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<AppState>,
req: Option<Json<MarkAllAsReadRequest>>,
Json(req): Json<MarkAllAsReadRequest>,
) -> AppResult<StatusCode> {
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

@ -185,7 +185,6 @@ pub async fn play_previous(
tag = "Room",
params(
("roomId" = String, Path, description = "Room ID"),
("beforeEntryId" = Option<String>, Query, description = "Legacy newest-first pagination cursor"),
("cursorEntryId" = Option<String>, Query, description = "Pagination cursor for the selected sort direction"),
("limit" = Option<i32>, Query, description = "Page size, up to 100"),
("sortDirection" = Option<i32>, Query, description = "Sort direction enum value; defaults to descending")

@ -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<CreateRoomPublishKeyRequest>,
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<AppState>,
Path(path): Path<RoomStreamPath>,
req: Option<Json<CreateRoomPublishKeyRequest>>,
Json(mut req): Json<CreateRoomPublishKeyRequest>,
) -> AppResult<Json<CreateRoomPublishKeyResponse>> {
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,

@ -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);

@ -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(())
}

@ -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,
}

@ -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;

@ -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;
}

@ -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. |

@ -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(

@ -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<LoadedConfig, String> {
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<LoadedConfig, String> {
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!(

Loading…
Cancel
Save