Workstream E (backend): wire tauri-specta — annotate commands, derive Type, generate-ready Builder
- Add #[specta::specta] to all 201 #[tauri::command] functions. - Derive specta::Type on all IPC DTOs (repository/types, settings, player/storage/ download command DTOs, player enums, jellyfin SessionInfo/NowPlayingItem/PlayState, ThumbnailCacheStats, DownloadInfo, CacheConfig, etc.). - Replace tauri::generate_handler! with a tauri_specta::Builder + collect_commands! in lib.rs (exports bindings.ts in debug builds). Two contract changes required by specta constraints (frontend migration follows): - specta caps command arity at 10 args: download_item_and_start / download_item / download_video now take a single request struct (params bundled, body unchanged via destructuring). - specta can't parse split serde rename_all: SessionInfo/NowPlayingItem/PlayState switched to rename_all = "PascalCase" (Jellyfin deserialization preserved; these now serialize PascalCase to the frontend). cargo check --lib is clean (0 errors). Frontend migration to bindings.ts is the next step.
This commit is contained in:
@@ -32,7 +32,7 @@ pub struct CredentialStoreWrapper(pub Mutex<CredentialStore>);
|
||||
pub struct ThumbnailCacheWrapper(pub Arc<ThumbnailCache>);
|
||||
|
||||
/// Server info returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerInfo {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
@@ -41,7 +41,7 @@ pub struct ServerInfo {
|
||||
}
|
||||
|
||||
/// User info returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserInfo {
|
||||
pub id: String,
|
||||
@@ -51,7 +51,7 @@ pub struct UserInfo {
|
||||
}
|
||||
|
||||
/// Active session info (for session restoration)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ActiveSession {
|
||||
pub user_id: String,
|
||||
@@ -63,7 +63,7 @@ pub struct ActiveSession {
|
||||
}
|
||||
|
||||
/// Security status info
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SecurityStatus {
|
||||
pub using_keyring: bool,
|
||||
@@ -72,6 +72,7 @@ pub struct SecurityStatus {
|
||||
|
||||
/// Initialize the database and run migrations
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_init(db: State<DatabaseWrapper>) -> Result<String, String> {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
Ok(database.path().to_string_lossy().to_string())
|
||||
@@ -79,6 +80,7 @@ pub fn storage_init(db: State<DatabaseWrapper>) -> Result<String, String> {
|
||||
|
||||
/// Get storage directory path (parent directory of the database file)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_get_path(db: State<DatabaseWrapper>) -> Result<String, String> {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let db_path = database.path();
|
||||
@@ -92,6 +94,7 @@ pub fn storage_get_path(db: State<DatabaseWrapper>) -> Result<String, String> {
|
||||
|
||||
/// Get database file size in bytes
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_get_size(db: State<DatabaseWrapper>) -> Result<Option<u64>, String> {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
Ok(database.file_size())
|
||||
@@ -99,6 +102,7 @@ pub fn storage_get_size(db: State<DatabaseWrapper>) -> Result<Option<u64>, Strin
|
||||
|
||||
/// Get security status (keyring vs encrypted file fallback)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_get_security_status(
|
||||
creds: State<CredentialStoreWrapper>,
|
||||
) -> Result<SecurityStatus, String> {
|
||||
@@ -117,6 +121,7 @@ pub fn storage_get_security_status(
|
||||
/// Save a server connection
|
||||
/// Uses INSERT ... ON CONFLICT to avoid triggering CASCADE DELETE on users
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_server(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
id: String,
|
||||
@@ -154,6 +159,7 @@ pub async fn storage_save_server(
|
||||
|
||||
/// Get all saved servers
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_servers(db: State<'_, DatabaseWrapper>) -> Result<Vec<ServerInfo>, String> {
|
||||
let db_service = {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
@@ -179,6 +185,7 @@ pub async fn storage_get_servers(db: State<'_, DatabaseWrapper>) -> Result<Vec<S
|
||||
|
||||
/// Delete a server and all associated data
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_delete_server(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
creds: State<'_, CredentialStoreWrapper>,
|
||||
@@ -221,6 +228,7 @@ pub async fn storage_delete_server(
|
||||
|
||||
/// Save a user account (token stored in secure storage, not database)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
creds: State<'_, CredentialStoreWrapper>,
|
||||
@@ -298,6 +306,7 @@ pub async fn storage_save_user(
|
||||
|
||||
/// Get users for a server
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_users(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -330,6 +339,7 @@ pub async fn storage_get_users(
|
||||
|
||||
/// Set a user as active (and deactivate all other users globally)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_set_active_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -378,6 +388,7 @@ pub async fn storage_set_active_user(
|
||||
|
||||
/// Get the active user for a server
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_active_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -408,6 +419,7 @@ pub async fn storage_get_active_user(
|
||||
|
||||
/// Get the active session (user + server + token) for session restoration
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_active_session(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
creds: State<'_, CredentialStoreWrapper>,
|
||||
@@ -483,6 +495,7 @@ pub async fn storage_get_active_session(
|
||||
|
||||
/// Get user's access token from secure storage
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_get_access_token(
|
||||
creds: State<CredentialStoreWrapper>,
|
||||
user_id: String,
|
||||
@@ -497,6 +510,7 @@ pub fn storage_get_access_token(
|
||||
|
||||
/// Delete a user account and their token from secure storage
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_delete_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
creds: State<'_, CredentialStoreWrapper>,
|
||||
@@ -529,7 +543,7 @@ pub async fn storage_delete_user(
|
||||
}
|
||||
|
||||
/// Playback progress info
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PlaybackProgress {
|
||||
pub item_id: String,
|
||||
@@ -542,6 +556,7 @@ pub struct PlaybackProgress {
|
||||
/// Update playback progress in local database
|
||||
/// This stores the progress locally for offline access and "continue watching"
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_update_playback_progress(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -593,6 +608,7 @@ pub async fn storage_update_playback_progress(
|
||||
/// Update playback progress with context in local database
|
||||
/// This stores the progress along with playback context (container vs single)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_update_playback_context(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -642,6 +658,7 @@ pub async fn storage_update_playback_context(
|
||||
|
||||
/// Mark item as played in local database
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_mark_played(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
smart_cache: State<'_, SmartCacheWrapper>,
|
||||
@@ -772,6 +789,7 @@ pub async fn storage_mark_played(
|
||||
|
||||
/// Get playback progress for an item
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_playback_progress(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -804,6 +822,7 @@ pub async fn storage_get_playback_progress(
|
||||
|
||||
/// Mark pending sync as completed for an item
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_mark_synced(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -828,6 +847,7 @@ pub async fn storage_mark_synced(
|
||||
/// Toggle favorite status for an item in local database
|
||||
/// This updates the is_favorite field and marks it for sync to Jellyfin
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_toggle_favorite(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -873,7 +893,7 @@ pub async fn storage_toggle_favorite(
|
||||
// =============================================================================
|
||||
|
||||
/// Cached library info returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CachedLibrary {
|
||||
pub id: String,
|
||||
@@ -884,7 +904,7 @@ pub struct CachedLibrary {
|
||||
}
|
||||
|
||||
/// Cached media item returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CachedItem {
|
||||
pub id: String,
|
||||
@@ -915,6 +935,7 @@ pub struct CachedItem {
|
||||
|
||||
/// Get cached libraries for a server
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_libraries(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -977,6 +998,7 @@ fn row_to_cached_item(row: &rusqlite::Row) -> rusqlite::Result<CachedItem> {
|
||||
|
||||
/// Get cached items with optional filtering
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_items(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -1043,6 +1065,7 @@ pub async fn storage_get_items(
|
||||
|
||||
/// Get a single cached item by ID
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_item(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
item_id: String,
|
||||
@@ -1070,6 +1093,7 @@ pub async fn storage_get_item(
|
||||
|
||||
/// Search cached items using FTS
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_search_items(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -1111,6 +1135,7 @@ pub async fn storage_search_items(
|
||||
|
||||
/// Save a library to the cache
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_library(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
id: String,
|
||||
@@ -1144,6 +1169,7 @@ pub async fn storage_save_library(
|
||||
|
||||
/// Save an item to the cache
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_item(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
item: CachedItem,
|
||||
@@ -1207,6 +1233,7 @@ pub async fn storage_save_item(
|
||||
|
||||
/// Get count of pending sync operations for a user
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_pending_sync_count(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
|
||||
Reference in New Issue
Block a user