mirror of https://github.com/synctv-org/synctv
feat: add playback history management (#431)
## Summary - add ordered cursor pagination for room playback history - add room-manager APIs to delete individual entries or clear history - publish history mutations through the existing playback realtime path - preserve live playback progress and cross-instance version fencing during mutations - document the new query parameters and refresh SQLx offline metadata ## Testing - `cargo check --workspace --all-targets` - `make clippy-check` - `cargo fmt --all -- --check` - core playback tests: 108 passed, 8 ignored - HTTP/OpenAPI tests: 226 passed, 59 ignored - PostgreSQL integration test for delete, clear, current-entry handling, and playback position continuitypull/436/head
parent
89b16ab21e
commit
9936e555d7
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "DELETE FROM room_playback_history\n WHERE room_id = $1 AND id = $2\n RETURNING id AS \"id!\"",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id!",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "10240b18dcfdd6baf6fc5ecf3f1f11148915c58c6facecfd83aa37f372569fa6"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "DELETE FROM room_playback_history WHERE room_id = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "2e33108da4743a3eece50f4600c750dd96cff512b3f5c3057b590c1215581c3b"
|
||||||
|
}
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT state.room_id AS \"room_id!: RoomId\",\n state.playing_media_id AS \"playing_media_id?: MediaId\",\n state.playing_playlist_id AS \"playing_playlist_id?: PlaylistId\",\n state.target AS \"target?: crate::models::ProviderTarget\",\n state.current_progress_id,\n state.history_cursor_id,\n COALESCE(progress.\"position\", 0.0) AS \"position!\",\n state.speed AS \"speed!\",\n state.is_playing AS \"is_playing!\",\n state.playback_generation AS \"playback_generation!\",\n state.updated_at AS \"updated_at!\",\n state.version AS \"version!\"\n FROM room_playback_state state\n LEFT JOIN room_playback_progress progress\n ON progress.id = state.current_progress_id\n WHERE state.room_id = $1\n FOR UPDATE OF state",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "room_id!: RoomId",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "room_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "playing_media_id?: MediaId",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "playing_media_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "playing_playlist_id?: PlaylistId",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "playing_playlist_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "target?: crate::models::ProviderTarget",
|
||||||
|
"type_info": "Jsonb",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "current_progress_id",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "current_progress_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "history_cursor_id",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "history_cursor_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
|
"name": "position!",
|
||||||
|
"type_info": "Float8",
|
||||||
|
"origin": "Expression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 7,
|
||||||
|
"name": "speed!",
|
||||||
|
"type_info": "Float8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "speed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 8,
|
||||||
|
"name": "is_playing!",
|
||||||
|
"type_info": "Bool",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "is_playing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 9,
|
||||||
|
"name": "playback_generation!",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "playback_generation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 10,
|
||||||
|
"name": "updated_at!",
|
||||||
|
"type_info": "Timestamptz",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "updated_at"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 11,
|
||||||
|
"name": "version!",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_state",
|
||||||
|
"name": "version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "ddd338c0963cc88111adcb605d8d83d539bfc5a7f3cda449e57437251ad3500e"
|
||||||
|
}
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT history.id AS \"id!\", history.room_id AS \"room_id!: RoomId\", history.sequence AS \"sequence!\",\n history.media_id AS \"media_id?: MediaId\", history.playlist_id AS \"playlist_id?: PlaylistId\",\n history.target AS \"target?: crate::models::ProviderTarget\",\n history.position_seconds, history.selected_by_user_id AS \"selected_by_user_id?: UserId\",\n history.media_name,\n history.playlist_name,\n COALESCE(media.source_provider, playlist.source_provider) AS \"source_provider?: crate::models::SourceProvider\",\n COALESCE(media.provider_instance_name, playlist.provider_instance_name)\n AS \"provider_instance_name?\",\n history.created_at, history.updated_at\n FROM room_playback_history history\n LEFT JOIN media\n ON media.id = history.media_id AND media.room_id = history.room_id\n LEFT JOIN playlists playlist\n ON playlist.id = history.playlist_id AND playlist.room_id = history.room_id\n WHERE history.room_id = $1\n AND ($2::bigint IS NULL OR history.id < $2)\n ORDER BY history.sequence DESC, history.id DESC\n LIMIT $3",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id!",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "room_id!: RoomId",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "room_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "sequence!",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "sequence"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "media_id?: MediaId",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "media_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "playlist_id?: PlaylistId",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "playlist_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "target?: crate::models::ProviderTarget",
|
||||||
|
"type_info": "Jsonb",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
|
"name": "position_seconds",
|
||||||
|
"type_info": "Float8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "position_seconds"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 7,
|
||||||
|
"name": "selected_by_user_id?: UserId",
|
||||||
|
"type_info": "Int8",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "selected_by_user_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 8,
|
||||||
|
"name": "media_name",
|
||||||
|
"type_info": "Text",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "media_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 9,
|
||||||
|
"name": "playlist_name",
|
||||||
|
"type_info": "Text",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "playlist_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 10,
|
||||||
|
"name": "source_provider?: crate::models::SourceProvider",
|
||||||
|
"type_info": "Int2",
|
||||||
|
"origin": "Expression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 11,
|
||||||
|
"name": "provider_instance_name?",
|
||||||
|
"type_info": "Varchar",
|
||||||
|
"origin": "Expression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 12,
|
||||||
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamptz",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "created_at"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 13,
|
||||||
|
"name": "updated_at",
|
||||||
|
"type_info": "Timestamptz",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "room_playback_history",
|
||||||
|
"name": "updated_at"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "f58607d0fa58276a07b675c779e94ae84b06bb6c586dff2084c299c0c233446a"
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue