diff --git a/Cargo.lock b/Cargo.lock index 25a3d5c14482..7ea618227936 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6411,7 +6411,7 @@ dependencies = [ [[package]] name = "rspc" version = "0.1.4" -source = "git+https://github.com/oscartbeaumont/rspc?rev=9cb64def32aef5a987f9f06f727b4160c321b5f8#9cb64def32aef5a987f9f06f727b4160c321b5f8" +source = "git+https://github.com/oscartbeaumont/rspc?rev=745f0b210f6d268a4cef5d43b595e88d99ac269f#745f0b210f6d268a4cef5d43b595e88d99ac269f" dependencies = [ "futures", "futures-channel", diff --git a/Cargo.toml b/Cargo.toml index e1146426bc0f..b6a59773ad6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,5 +45,5 @@ if-watch = { git = "https://github.com/oscartbeaumont/if-watch", rev = "410e8e1d mdns-sd = { git = "https://github.com/oscartbeaumont/mdns-sd", rev = "45515a98e9e408c102871abaa5a9bff3bee0cbe8" } # TODO: Do upstream PR -rspc = { git = "https://github.com/oscartbeaumont/rspc", rev = "9cb64def32aef5a987f9f06f727b4160c321b5f8" } +rspc = { git = "https://github.com/oscartbeaumont/rspc", rev = "745f0b210f6d268a4cef5d43b595e88d99ac269f" } httpz = { git = "https://github.com/oscartbeaumont/httpz", rev = "a5185f2ed2fdefeb2f582dce38a692a1bf76d1d6" } diff --git a/core/Cargo.toml b/core/Cargo.toml index 735744916596..b8fc302a371b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -31,7 +31,7 @@ sd-file-ext = { path = "../crates/file-ext" } sd-sync = { path = "../crates/sync" } sd-p2p = { path = "../crates/p2p", features = ["specta", "serde"] } -rspc = { workspace = true, features = ["uuid", "chrono", "tracing", "unstable"] } +rspc = { workspace = true, features = ["uuid", "chrono", "tracing", "alpha", "unstable"] } httpz = { workspace = true } prisma-client-rust = { workspace = true } specta = { workspace = true } diff --git a/core/src/api/files.rs b/core/src/api/files.rs index d2d14ee5dcc9..1bf0df69d188 100644 --- a/core/src/api/files.rs +++ b/core/src/api/files.rs @@ -1,6 +1,6 @@ use crate::{ + api::utils::library, invalidate_query, - library::Library, location::{file_path_helper::MaterializedPath, find_location, LocationError}, object::fs::{ copy::FileCopierJobInit, cut::FileCutterJobInit, decrypt::FileDecryptorJobInit, @@ -9,132 +9,139 @@ use crate::{ prisma::{location, object}, }; -use rspc::ErrorCode; +use rspc::{alpha::AlphaRouter, ErrorCode}; use serde::Deserialize; use specta::Type; use std::path::Path; use tokio::fs; -use super::{utils::LibraryRequest, RouterBuilder}; +use super::{Ctx, R}; -pub(crate) fn mount() -> RouterBuilder { - ::new() - .library_query("get", |t| { +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("get", { #[derive(Type, Deserialize)] pub struct GetArgs { pub id: i32, } - t(|_, args: GetArgs, library: Library| async move { - Ok(library - .db - .object() - .find_unique(object::id::equals(args.id)) - .include(object::include!({ file_paths media_data })) - .exec() - .await?) - }) + R.with2(library()) + .query(|(_, library), args: GetArgs| async move { + Ok(library + .db + .object() + .find_unique(object::id::equals(args.id)) + .include(object::include!({ file_paths media_data })) + .exec() + .await?) + }) }) - .library_mutation("setNote", |t| { + .procedure("setNote", { #[derive(Type, Deserialize)] pub struct SetNoteArgs { pub id: i32, pub note: Option, } - t(|_, args: SetNoteArgs, library: Library| async move { - library - .db - .object() - .update( - object::id::equals(args.id), - vec![object::note::set(args.note)], - ) - .exec() - .await?; + R.with2(library()) + .mutation(|(_, library), args: SetNoteArgs| async move { + library + .db + .object() + .update( + object::id::equals(args.id), + vec![object::note::set(args.note)], + ) + .exec() + .await?; - invalidate_query!(library, "locations.getExplorerData"); - invalidate_query!(library, "tags.getExplorerData"); + invalidate_query!(library, "locations.getExplorerData"); + invalidate_query!(library, "tags.getExplorerData"); - Ok(()) - }) + Ok(()) + }) }) - .library_mutation("setFavorite", |t| { + .procedure("setFavorite", { #[derive(Type, Deserialize)] pub struct SetFavoriteArgs { pub id: i32, pub favorite: bool, } - t(|_, args: SetFavoriteArgs, library: Library| async move { - library - .db - .object() - .update( - object::id::equals(args.id), - vec![object::favorite::set(args.favorite)], - ) - .exec() - .await?; + R.with2(library()) + .mutation(|(_, library), args: SetFavoriteArgs| async move { + library + .db + .object() + .update( + object::id::equals(args.id), + vec![object::favorite::set(args.favorite)], + ) + .exec() + .await?; - invalidate_query!(library, "locations.getExplorerData"); - invalidate_query!(library, "tags.getExplorerData"); + invalidate_query!(library, "locations.getExplorerData"); + invalidate_query!(library, "tags.getExplorerData"); - Ok(()) - }) + Ok(()) + }) }) - .library_mutation("delete", |t| { - t(|_, id: i32, library: Library| async move { - library - .db - .object() - .delete(object::id::equals(id)) - .exec() - .await?; + .procedure("delete", { + R.with2(library()) + .mutation(|(_, library), id: i32| async move { + library + .db + .object() + .delete(object::id::equals(id)) + .exec() + .await?; - invalidate_query!(library, "locations.getExplorerData"); - Ok(()) - }) + invalidate_query!(library, "locations.getExplorerData"); + Ok(()) + }) }) - .library_mutation("encryptFiles", |t| { - t( - |_, args: FileEncryptorJobInit, library: Library| async move { + .procedure("encryptFiles", { + R.with2(library()) + .mutation(|(_, library), args: FileEncryptorJobInit| async move { library.spawn_job(args).await.map_err(Into::into) - }, - ) + }) }) - .library_mutation("decryptFiles", |t| { - t( - |_, args: FileDecryptorJobInit, library: Library| async move { + .procedure("decryptFiles", { + R.with2(library()) + .mutation(|(_, library), args: FileDecryptorJobInit| async move { library.spawn_job(args).await.map_err(Into::into) - }, - ) + }) }) - .library_mutation("deleteFiles", |t| { - t(|_, args: FileDeleterJobInit, library: Library| async move { - library.spawn_job(args).await.map_err(Into::into) - }) + .procedure("deleteFiles", { + R.with2(library()) + .mutation(|(_, library), args: FileDeleterJobInit| async move { + library.spawn_job(args).await.map_err(Into::into) + }) }) - .library_mutation("eraseFiles", |t| { - t(|_, args: FileEraserJobInit, library: Library| async move { - library.spawn_job(args).await.map_err(Into::into) - }) + .procedure("eraseFiles", { + R.with2(library()) + .mutation(|(_, library), args: FileEraserJobInit| async move { + library.spawn_job(args).await.map_err(Into::into) + }) }) - .library_mutation("duplicateFiles", |t| { - t(|_, args: FileCopierJobInit, library: Library| async move { - library.spawn_job(args).await.map_err(Into::into) - }) + .procedure("duplicateFiles", { + R.with2(library()) + .mutation(|(_, library), args: FileCopierJobInit| async move { + library.spawn_job(args).await.map_err(Into::into) + }) }) - .library_mutation("copyFiles", |t| { - t(|_, args: FileCopierJobInit, library: Library| async move { - library.spawn_job(args).await.map_err(Into::into) - }) + .procedure("copyFiles", { + R.with2(library()) + .mutation(|(_, library), args: FileCopierJobInit| async move { + library.spawn_job(args).await.map_err(Into::into) + }) }) - .library_mutation("cutFiles", |t| { - t(|_, args: FileCutterJobInit, library: Library| async move { - library.spawn_job(args).await.map_err(Into::into) - }) + .procedure("cutFiles", { + R.with2(library()) + .mutation(|(_, library), args: FileCutterJobInit| async move { + library.spawn_job(args).await.map_err(Into::into) + }) }) - .library_mutation("renameFile", |t| { + .procedure("renameFile", { #[derive(Type, Deserialize)] pub struct RenameFileArgs { pub location_id: i32, @@ -142,14 +149,13 @@ pub(crate) fn mount() -> RouterBuilder { pub new_file_name: String, } - t( - |_, + R.with2(library()).mutation( + |(_, library), RenameFileArgs { location_id, file_name, new_file_name, - }: RenameFileArgs, - library: Library| async move { + }: RenameFileArgs| async move { let location = find_location(&library, location_id) .select(location::select!({ path })) .exec() diff --git a/core/src/api/jobs.rs b/core/src/api/jobs.rs index af66071e2e98..d4d7ac2f84b1 100644 --- a/core/src/api/jobs.rs +++ b/core/src/api/jobs.rs @@ -8,46 +8,50 @@ use crate::{ }, }; +use rspc::alpha::AlphaRouter; use serde::Deserialize; use specta::Type; use std::path::PathBuf; use uuid::Uuid; -use super::{utils::LibraryRequest, CoreEvent, RouterBuilder}; +use super::{utils::library, CoreEvent, Ctx, R}; -pub(crate) fn mount() -> RouterBuilder { - ::new() - .library_query("getRunning", |t| { - t(|ctx, _: (), _| async move { Ok(ctx.jobs.get_running().await) }) +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("getRunning", { + R.with2(library()) + .query(|(ctx, _), _: ()| async move { Ok(ctx.jobs.get_running().await) }) }) - .library_query("getHistory", |t| { - t(|_, _: (), library| async move { + .procedure("getHistory", { + R.with2(library()).query(|(_, library), _: ()| async move { JobManager::get_history(&library).await.map_err(Into::into) }) }) - .library_mutation("clear", |t| { - t(|_, id: Uuid, library| async move { - JobManager::clear_job(id, &library) - .await - .map_err(Into::into) - }) + .procedure("clear", { + R.with2(library()) + .mutation(|(_, library), id: Uuid| async move { + JobManager::clear_job(id, &library) + .await + .map_err(Into::into) + }) }) - .library_mutation("clearAll", |t| { - t(|_, _: (), library| async move { - JobManager::clear_all_jobs(&library) - .await - .map_err(Into::into) - }) + .procedure("clearAll", { + R.with2(library()) + .mutation(|(_, library), _: ()| async move { + JobManager::clear_all_jobs(&library) + .await + .map_err(Into::into) + }) }) - .library_mutation("generateThumbsForLocation", |t| { + .procedure("generateThumbsForLocation", { #[derive(Type, Deserialize)] pub struct GenerateThumbsForLocationArgs { pub id: i32, pub path: PathBuf, } - t( - |_, args: GenerateThumbsForLocationArgs, library| async move { + R.with2(library()).mutation( + |(_, library), args: GenerateThumbsForLocationArgs| async move { let Some(location) = find_location(&library, args.id).exec().await? else { return Err(LocationError::IdNotFound(args.id).into()); }; @@ -63,62 +67,65 @@ pub(crate) fn mount() -> RouterBuilder { }, ) }) - .library_mutation("objectValidator", |t| { + .procedure("objectValidator", { #[derive(Type, Deserialize)] pub struct ObjectValidatorArgs { pub id: i32, pub path: PathBuf, } - t(|_, args: ObjectValidatorArgs, library| async move { - if find_location(&library, args.id).exec().await?.is_none() { - return Err(LocationError::IdNotFound(args.id).into()); - } + R.with2(library()) + .mutation(|(_, library), args: ObjectValidatorArgs| async move { + if find_location(&library, args.id).exec().await?.is_none() { + return Err(LocationError::IdNotFound(args.id).into()); + } - library - .spawn_job(ObjectValidatorJobInit { - location_id: args.id, - path: args.path, - background: true, - }) - .await - .map_err(Into::into) - }) + library + .spawn_job(ObjectValidatorJobInit { + location_id: args.id, + path: args.path, + background: true, + }) + .await + .map_err(Into::into) + }) }) - .library_mutation("identifyUniqueFiles", |t| { + .procedure("identifyUniqueFiles", { #[derive(Type, Deserialize)] pub struct IdentifyUniqueFilesArgs { pub id: i32, pub path: PathBuf, } - t(|_, args: IdentifyUniqueFilesArgs, library| async move { - let Some(location) = find_location(&library, args.id).exec().await? else { - return Err(LocationError::IdNotFound(args.id).into()); - }; + R.with2(library()) + .mutation(|(_, library), args: IdentifyUniqueFilesArgs| async move { + let Some(location) = find_location(&library, args.id).exec().await? else { + return Err(LocationError::IdNotFound(args.id).into()); + }; - library - .spawn_job(FileIdentifierJobInit { - location, - sub_path: Some(args.path), - }) - .await - .map_err(Into::into) - }) + library + .spawn_job(FileIdentifierJobInit { + location, + sub_path: Some(args.path), + }) + .await + .map_err(Into::into) + }) }) - .library_subscription("newThumbnail", |t| { - t(|ctx, _: (), _| { - // TODO: Only return event for the library that was subscribed to + .procedure("newThumbnail", { + R.with2(library()) + .subscription(|(ctx, _), _: ()| async move { + // TODO: Only return event for the library that was subscribed to - let mut event_bus_rx = ctx.event_bus.0.subscribe(); - async_stream::stream! { - while let Ok(event) = event_bus_rx.recv().await { - match event { - CoreEvent::NewThumbnail { cas_id } => yield cas_id, - _ => {} + let mut event_bus_rx = ctx.event_bus.0.subscribe(); + async_stream::stream! { + while let Ok(event) = event_bus_rx.recv().await { + match event { + CoreEvent::NewThumbnail { cas_id } => yield cas_id, + _ => {} + } } } - } - }) + }) }) } diff --git a/core/src/api/keys.rs b/core/src/api/keys.rs index 4445e0cbee8f..6b4d739e51eb 100644 --- a/core/src/api/keys.rs +++ b/core/src/api/keys.rs @@ -1,3 +1,4 @@ +use rspc::alpha::AlphaRouter; use sd_crypto::keys::keymanager::{StoredKey, StoredKeyType}; use sd_crypto::primitives::SECRET_KEY_IDENTIFIER; use sd_crypto::types::{Algorithm, HashingAlgorithm, OnboardingConfig, SecretKeyString}; @@ -12,7 +13,8 @@ use uuid::Uuid; use crate::util::db::write_storedkey_to_db; use crate::{invalidate_query, prisma::key}; -use super::{utils::LibraryRequest, RouterBuilder}; +use super::utils::library; +use super::{Ctx, R}; #[derive(Type, Deserialize)] pub struct KeyAddArgs { @@ -49,59 +51,67 @@ pub struct AutomountUpdateArgs { status: bool, } -pub(crate) fn mount() -> RouterBuilder { - RouterBuilder::new() - .library_query("list", |t| { - t(|_, _: (), library| async move { Ok(library.key_manager.dump_keystore()) }) +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("list", { + R.with2(library()) + .query(|(_, library), _: ()| async move { Ok(library.key_manager.dump_keystore()) }) }) // do not unlock the key manager until this route returns true - .library_query("isUnlocked", |t| { - t(|_, _: (), library| async move { Ok(library.key_manager.is_unlocked().await) }) + .procedure("isUnlocked", { + R.with2(library()).query(|(_, library), _: ()| async move { + Ok(library.key_manager.is_unlocked().await) + }) }) - .library_query("isSetup", |t| { - t(|_, _: (), library| async move { + .procedure("isSetup", { + R.with2(library()).query(|(_, library), _: ()| async move { Ok(!library.db.key().find_many(vec![]).exec().await?.is_empty()) }) }) - .library_mutation("setup", |t| { - t(|_, config: OnboardingConfig, library| async move { - let root_key = library.key_manager.onboarding(config, library.id).await?; - write_storedkey_to_db(&library.db, &root_key).await?; - library - .key_manager - .populate_keystore(vec![root_key]) - .await?; + .procedure("setup", { + R.with2(library()) + .mutation(|(_, library), config: OnboardingConfig| async move { + let root_key = library.key_manager.onboarding(config, library.id).await?; + write_storedkey_to_db(&library.db, &root_key).await?; + library + .key_manager + .populate_keystore(vec![root_key]) + .await?; - invalidate_query!(library, "keys.isSetup"); - invalidate_query!(library, "keys.isUnlocked"); + invalidate_query!(library, "keys.isSetup"); + invalidate_query!(library, "keys.isUnlocked"); - Ok(()) - }) + Ok(()) + }) }) // this is so we can show the key as mounted in the UI - .library_query("listMounted", |t| { - t(|_, _: (), library| async move { Ok(library.key_manager.get_mounted_uuids()) }) - }) - .library_query("getKey", |t| { - t(|_, key_uuid: Uuid, library| async move { - Ok(library - .key_manager - .get_key(key_uuid) - .await? - .expose() - .clone()) + .procedure("listMounted", { + R.with2(library()).query(|(_, library), _: ()| async move { + Ok(library.key_manager.get_mounted_uuids()) }) }) - .library_mutation("mount", |t| { - t(|_, key_uuid: Uuid, library| async move { - library.key_manager.mount(key_uuid).await?; - // we also need to dispatch jobs that automatically decrypt preview media and metadata here - invalidate_query!(library, "keys.listMounted"); - Ok(()) - }) + .procedure("getKey", { + R.with2(library()) + .query(|(_, library), key_uuid: Uuid| async move { + Ok(library + .key_manager + .get_key(key_uuid) + .await? + .expose() + .clone()) + }) + }) + .procedure("mount", { + R.with2(library()) + .mutation(|(_, library), key_uuid: Uuid| async move { + library.key_manager.mount(key_uuid).await?; + // we also need to dispatch jobs that automatically decrypt preview media and metadata here + invalidate_query!(library, "keys.listMounted"); + Ok(()) + }) }) - .library_query("getSecretKey", |t| { - t(|_, _: (), library| async move { + .procedure("getSecretKey", { + R.with2(library()).query(|(_, library), _: ()| async move { if library .key_manager .keyring_contains_valid_secret_key(library.id) @@ -121,277 +131,296 @@ pub(crate) fn mount() -> RouterBuilder { } }) }) - .library_mutation("unmount", |t| { - t(|_, key_uuid: Uuid, library| async move { - library.key_manager.unmount(key_uuid)?; - // we also need to delete all in-memory decrypted data associated with this key - invalidate_query!(library, "keys.listMounted"); - Ok(()) - }) + .procedure("unmount", { + R.with2(library()) + .mutation(|(_, library), key_uuid: Uuid| async move { + library.key_manager.unmount(key_uuid)?; + // we also need to delete all in-memory decrypted data associated with this key + invalidate_query!(library, "keys.listMounted"); + Ok(()) + }) }) - .library_mutation("clearMasterPassword", |t| { - t(|_, _: (), library| async move { - // This technically clears the root key, but it means the same thing to the frontend - library.key_manager.clear_root_key().await?; + .procedure("clearMasterPassword", { + R.with2(library()) + .mutation(|(_, library), _: ()| async move { + // This technically clears the root key, but it means the same thing to the frontend + library.key_manager.clear_root_key().await?; + + invalidate_query!(library, "keys.isUnlocked"); + Ok(()) + }) + }) + .procedure("syncKeyToLibrary", { + R.with2(library()) + .mutation(|(_, library), key_uuid: Uuid| async move { + let key = library.key_manager.sync_to_database(key_uuid).await?; - invalidate_query!(library, "keys.isUnlocked"); - Ok(()) - }) + // does not check that the key doesn't exist before writing + write_storedkey_to_db(&library.db, &key).await?; + + invalidate_query!(library, "keys.list"); + Ok(()) + }) }) - .library_mutation("syncKeyToLibrary", |t| { - t(|_, key_uuid: Uuid, library| async move { - let key = library.key_manager.sync_to_database(key_uuid).await?; + .procedure("updateAutomountStatus", { + R.with2(library()) + .mutation(|(_, library), args: AutomountUpdateArgs| async move { + if !library.key_manager.is_memory_only(args.uuid).await? { + library + .key_manager + .change_automount_status(args.uuid, args.status) + .await?; + + library + .db + .key() + .update( + key::uuid::equals(args.uuid.to_string()), + vec![key::SetParam::SetAutomount(args.status)], + ) + .exec() + .await?; - // does not check that the key doesn't exist before writing - write_storedkey_to_db(&library.db, &key).await?; + invalidate_query!(library, "keys.list"); + } - invalidate_query!(library, "keys.list"); - Ok(()) - }) + Ok(()) + }) }) - .library_mutation("updateAutomountStatus", |t| { - t(|_, args: AutomountUpdateArgs, library| async move { - if !library.key_manager.is_memory_only(args.uuid).await? { + .procedure("deleteFromLibrary", { + R.with2(library()) + .mutation(|(_, library), key_uuid: Uuid| async move { + if !library.key_manager.is_memory_only(key_uuid).await? { + library + .db + .key() + .delete(key::uuid::equals(key_uuid.to_string())) + .exec() + .await?; + } + + library.key_manager.remove_key(key_uuid).await?; + + // we also need to delete all in-memory decrypted data associated with this key + invalidate_query!(library, "keys.list"); + invalidate_query!(library, "keys.listMounted"); + invalidate_query!(library, "keys.getDefault"); + Ok(()) + }) + }) + .procedure("unlockKeyManager", { + R.with2(library()) + .mutation(|(_, library), args: UnlockKeyManagerArgs| async move { + let secret_key = + (!args.secret_key.expose().is_empty()).then_some(args.secret_key); + library .key_manager - .change_automount_status(args.uuid, args.status) + .unlock( + args.password, + secret_key.map(SecretKeyString), + library.id, + || invalidate_query!(library, "keys.isKeyManagerUnlocking"), + ) .await?; - library + invalidate_query!(library, "keys.isUnlocked"); + + let automount = library .db .key() - .update( - key::uuid::equals(args.uuid.to_string()), - vec![key::SetParam::SetAutomount(args.status)], - ) + .find_many(vec![key::automount::equals(true)]) .exec() .await?; - invalidate_query!(library, "keys.list"); - } + for key in automount { + library + .key_manager + .mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?) + .await?; - Ok(()) - }) + invalidate_query!(library, "keys.listMounted"); + } + + Ok(()) + }) }) - .library_mutation("deleteFromLibrary", |t| { - t(|_, key_uuid: Uuid, library| async move { - if !library.key_manager.is_memory_only(key_uuid).await? { + .procedure("setDefault", { + R.with2(library()) + .mutation(|(_, library), key_uuid: Uuid| async move { + library.key_manager.set_default(key_uuid).await?; + library .db .key() - .delete(key::uuid::equals(key_uuid.to_string())) + .update_many( + vec![key::default::equals(true)], + vec![key::SetParam::SetDefault(false)], + ) .exec() .await?; - } - - library.key_manager.remove_key(key_uuid).await?; - - // we also need to delete all in-memory decrypted data associated with this key - invalidate_query!(library, "keys.list"); - invalidate_query!(library, "keys.listMounted"); - invalidate_query!(library, "keys.getDefault"); - Ok(()) - }) - }) - .library_mutation("unlockKeyManager", |t| { - t(|_, args: UnlockKeyManagerArgs, library| async move { - let secret_key = (!args.secret_key.expose().is_empty()).then_some(args.secret_key); - library - .key_manager - .unlock( - args.password, - secret_key.map(SecretKeyString), - library.id, - || invalidate_query!(library, "keys.isKeyManagerUnlocking"), - ) - .await?; - - invalidate_query!(library, "keys.isUnlocked"); - - let automount = library - .db - .key() - .find_many(vec![key::automount::equals(true)]) - .exec() - .await?; - - for key in automount { library - .key_manager - .mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?) + .db + .key() + .update( + key::uuid::equals(key_uuid.to_string()), + vec![key::SetParam::SetDefault(true)], + ) + .exec() .await?; - invalidate_query!(library, "keys.listMounted"); - } - - Ok(()) - }) + invalidate_query!(library, "keys.getDefault"); + Ok(()) + }) }) - .library_mutation("setDefault", |t| { - t(|_, key_uuid: Uuid, library| async move { - library.key_manager.set_default(key_uuid).await?; - - library - .db - .key() - .update_many( - vec![key::default::equals(true)], - vec![key::SetParam::SetDefault(false)], - ) - .exec() - .await?; - - library - .db - .key() - .update( - key::uuid::equals(key_uuid.to_string()), - vec![key::SetParam::SetDefault(true)], - ) - .exec() - .await?; - - invalidate_query!(library, "keys.getDefault"); - Ok(()) + .procedure("getDefault", { + R.with2(library()).query(|(_, library), _: ()| async move { + library.key_manager.get_default().await.ok() }) }) - .library_query("getDefault", |t| { - t(|_, _: (), library| async move { library.key_manager.get_default().await.ok() }) - }) - .library_query("isKeyManagerUnlocking", |t| { - t(|_, _: (), library| async move { library.key_manager.is_unlocking().await.ok() }) - }) - .library_mutation("unmountAll", |t| { - t(|_, _: (), library| async move { - library.key_manager.empty_keymount(); - invalidate_query!(library, "keys.listMounted"); - Ok(()) + .procedure("isKeyManagerUnlocking", { + R.with2(library()).query(|(_, library), _: ()| async move { + library.key_manager.is_unlocking().await.ok() }) }) - // this also mounts the key - .library_mutation("add", |t| { - t(|_, args: KeyAddArgs, library| async move { - // register the key with the keymanager - let uuid = library - .key_manager - .add_to_keystore( - args.key, - args.algorithm, - args.hashing_algorithm, - !args.library_sync, - args.automount, - None, - ) - .await?; - - if args.library_sync { - write_storedkey_to_db( - &library.db, - &library.key_manager.access_keystore(uuid).await?, - ) - .await?; - - if args.automount { - library - .db - .key() - .update( - key::uuid::equals(uuid.to_string()), - vec![key::SetParam::SetAutomount(true)], - ) - .exec() - .await?; + .procedure("unmountAll", { + R.with2(library()) + .mutation(|(_, library), _: ()| async move { + library.key_manager.empty_keymount(); + invalidate_query!(library, "keys.listMounted"); + Ok(()) + }) + }) + .procedure("add", { + // this also mounts the key + R.with2(library()) + .mutation(|(_, library), args: KeyAddArgs| async move { + // register the key with the keymanager + let uuid = library + .key_manager + .add_to_keystore( + args.key, + args.algorithm, + args.hashing_algorithm, + !args.library_sync, + args.automount, + None, + ) + .await?; + + if args.library_sync { + write_storedkey_to_db( + &library.db, + &library.key_manager.access_keystore(uuid).await?, + ) + .await?; + + if args.automount { + library + .db + .key() + .update( + key::uuid::equals(uuid.to_string()), + vec![key::SetParam::SetAutomount(true)], + ) + .exec() + .await?; + } } - } - library.key_manager.mount(uuid).await?; + library.key_manager.mount(uuid).await?; - invalidate_query!(library, "keys.list"); - invalidate_query!(library, "keys.listMounted"); - Ok(()) - }) + invalidate_query!(library, "keys.list"); + invalidate_query!(library, "keys.listMounted"); + Ok(()) + }) + }) + .procedure("backupKeystore", { + R.with2(library()) + .mutation(|(_, library), path: PathBuf| async move { + // dump all stored keys that are in the key manager (maybe these should be taken from prisma as this will include even "non-sync with library" keys) + let mut stored_keys = library.key_manager.dump_keystore(); + + // include the verification key at the time of backup + stored_keys.push(library.key_manager.get_verification_key().await?); + + // exclude all memory-only keys + stored_keys.retain(|k| !k.memory_only); + + let mut output_file = File::create(path).await.map_err(Error::Io)?; + output_file + .write_all( + &serde_json::to_vec(&stored_keys).map_err(|_| Error::Serialization)?, + ) + .await + .map_err(Error::Io)?; + Ok(()) + }) }) - .library_mutation("backupKeystore", |t| { - t(|_, path: PathBuf, library| async move { - // dump all stored keys that are in the key manager (maybe these should be taken from prisma as this will include even "non-sync with library" keys) - let mut stored_keys = library.key_manager.dump_keystore(); + .procedure("restoreKeystore", { + R.with2(library()) + .mutation(|(_, library), args: RestoreBackupArgs| async move { + let mut input_file = File::open(args.path).await.map_err(Error::Io)?; - // include the verification key at the time of backup - stored_keys.push(library.key_manager.get_verification_key().await?); + let mut backup = Vec::new(); - // exclude all memory-only keys - stored_keys.retain(|k| !k.memory_only); + input_file + .read_to_end(&mut backup) + .await + .map_err(Error::Io)?; - let mut output_file = File::create(path).await.map_err(Error::Io)?; - output_file - .write_all(&serde_json::to_vec(&stored_keys).map_err(|_| Error::Serialization)?) - .await - .map_err(Error::Io)?; - Ok(()) - }) - }) - .library_mutation("restoreKeystore", |t| { - t(|_, args: RestoreBackupArgs, library| async move { - let mut input_file = File::open(args.path).await.map_err(Error::Io)?; + let stored_keys: Vec = + serde_json::from_slice(&backup).map_err(|_| Error::Serialization)?; - let mut backup = Vec::new(); + let updated_keys = library + .key_manager + .import_keystore_backup( + args.password, + SecretKeyString(args.secret_key), + &stored_keys, + ) + .await?; - input_file - .read_to_end(&mut backup) - .await - .map_err(Error::Io)?; + for key in &updated_keys { + write_storedkey_to_db(&library.db, key).await?; + } - let stored_keys: Vec = - serde_json::from_slice(&backup).map_err(|_| Error::Serialization)?; + invalidate_query!(library, "keys.list"); + invalidate_query!(library, "keys.listMounted"); - let updated_keys = library - .key_manager - .import_keystore_backup( - args.password, - SecretKeyString(args.secret_key), - &stored_keys, - ) - .await?; - - for key in &updated_keys { - write_storedkey_to_db(&library.db, key).await?; - } + Ok(TryInto::::try_into(updated_keys.len()).unwrap()) // We convert from `usize` (bigint type) to `u32` (number type) because rspc doesn't support bigints. + }) + }) + .procedure("changeMasterPassword", { + R.with2(library()) + .mutation(|(_, library), args: MasterPasswordChangeArgs| async move { + let verification_key = library + .key_manager + .change_master_password( + args.password, + args.algorithm, + args.hashing_algorithm, + library.id, + ) + .await?; - invalidate_query!(library, "keys.list"); - invalidate_query!(library, "keys.listMounted"); + invalidate_query!(library, "keys.getSecretKey"); - Ok(TryInto::::try_into(updated_keys.len()).unwrap()) // We convert from `usize` (bigint type) to `u32` (number type) because rspc doesn't support bigints. - }) - }) - .library_mutation("changeMasterPassword", |t| { - t(|_, args: MasterPasswordChangeArgs, library| async move { - let verification_key = library - .key_manager - .change_master_password( - args.password, - args.algorithm, - args.hashing_algorithm, - library.id, - ) - .await?; - - invalidate_query!(library, "keys.getSecretKey"); - - // remove old root key if present - library - .db - .key() - .delete_many(vec![key::key_type::equals( - serde_json::to_string(&StoredKeyType::Root).unwrap(), - )]) - .exec() - .await?; - - // write the new verification key - write_storedkey_to_db(&library.db, &verification_key).await?; - - Ok(()) - }) + // remove old root key if present + library + .db + .key() + .delete_many(vec![key::key_type::equals( + serde_json::to_string(&StoredKeyType::Root).unwrap(), + )]) + .exec() + .await?; + + // write the new verification key + write_storedkey_to_db(&library.db, &verification_key).await?; + + Ok(()) + }) }) } diff --git a/core/src/api/libraries.rs b/core/src/api/libraries.rs index ab0af87344a0..beec767d4359 100644 --- a/core/src/api/libraries.rs +++ b/core/src/api/libraries.rs @@ -1,28 +1,31 @@ use crate::{ invalidate_query, - library::{Library, LibraryConfig}, + library::LibraryConfig, prisma::statistics, volume::{get_volumes, save_volume}, }; use chrono::Utc; +use rspc::alpha::AlphaRouter; use serde::Deserialize; use specta::Type; use tracing::debug; use uuid::Uuid; use super::{ - utils::{get_size, LibraryRequest}, - RouterBuilder, + utils::{get_size, library}, + Ctx, R, }; -pub(crate) fn mount() -> RouterBuilder { - ::new() - .query("list", |t| { - t(|ctx, _: ()| async move { ctx.library_manager.get_all_libraries_config().await }) +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("list", { + R.query( + |ctx, _: ()| async move { ctx.library_manager.get_all_libraries_config().await }, + ) }) - .library_query("getStatistics", |t| { - t(|_, _: (), library: Library| async move { + .procedure("getStatistics", { + R.with2(library()).query(|(_, library), _: ()| async move { let _statistics = library .db .statistics() @@ -83,13 +86,13 @@ pub(crate) fn mount() -> RouterBuilder { .await?) }) }) - .mutation("create", |t| { + .procedure("create", { #[derive(Deserialize, Type)] pub struct CreateLibraryArgs { name: String, } - t(|ctx, args: CreateLibraryArgs| async move { + R.mutation(|ctx, args: CreateLibraryArgs| async move { debug!("Creating library"); let new_library = ctx @@ -109,7 +112,7 @@ pub(crate) fn mount() -> RouterBuilder { Ok(new_library) }) }) - .mutation("edit", |t| { + .procedure("edit", { #[derive(Type, Deserialize)] pub struct EditLibraryArgs { pub id: Uuid, @@ -117,16 +120,17 @@ pub(crate) fn mount() -> RouterBuilder { pub description: Option, } - t(|node, args: EditLibraryArgs| async move { - Ok(node + R.mutation(|ctx, args: EditLibraryArgs| async move { + Ok(ctx .library_manager .edit(args.id, args.name, args.description) .await?) }) }) - .mutation("delete", |t| { - t(|ctx, id: Uuid| async move { Ok(ctx.library_manager.delete(id).await?) }) - }) + .procedure( + "delete", + R.mutation(|ctx, id: Uuid| async move { Ok(ctx.library_manager.delete(id).await?) }), + ) // .yolo_merge("peer.guest.", peer_guest_router()) // .yolo_merge("peer.host.", peer_host_router()) } diff --git a/core/src/api/locations.rs b/core/src/api/locations.rs index 7cf937bf5a56..53ab04c10431 100644 --- a/core/src/api/locations.rs +++ b/core/src/api/locations.rs @@ -13,11 +13,11 @@ use std::{ path::{PathBuf, MAIN_SEPARATOR, MAIN_SEPARATOR_STR}, }; -use rspc::{self, ErrorCode, RouterBuilderLike}; +use rspc::{self, alpha::AlphaRouter, ErrorCode}; use serde::{Deserialize, Serialize}; use specta::Type; -use super::{utils::LibraryRequest, Ctx, RouterBuilder}; +use super::{utils::library, Ctx, R}; #[derive(Serialize, Deserialize, Type, Debug)] #[serde(tag = "type")] @@ -50,10 +50,10 @@ pub struct ExplorerData { file_path::include!(file_path_with_object { object }); object::include!(object_with_file_paths { file_paths }); -pub(crate) fn mount() -> impl RouterBuilderLike { - ::new() - .library_query("list", |t| { - t(|_, _: (), library| async move { +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("list", { + R.with2(library()).query(|(_, library), _: ()| async move { Ok(library .db .location() @@ -63,18 +63,19 @@ pub(crate) fn mount() -> impl RouterBuilderLike { .await?) }) }) - .library_query("getById", |t| { - t(|_, location_id: i32, library| async move { - Ok(library - .db - .location() - .find_unique(location::id::equals(location_id)) - .include(location_with_indexer_rules::include()) - .exec() - .await?) - }) + .procedure("getById", { + R.with2(library()) + .query(|(_, library), location_id: i32| async move { + Ok(library + .db + .location() + .find_unique(location::id::equals(location_id)) + .include(location_with_indexer_rules::include()) + .exec() + .await?) + }) }) - .library_query("getExplorerData", |t| { + .procedure("getExplorerData", { #[derive(Clone, Serialize, Deserialize, Type, Debug)] pub struct LocationExplorerArgs { pub location_id: i32, @@ -84,248 +85,265 @@ pub(crate) fn mount() -> impl RouterBuilderLike { pub kind: Option>, } - t(|_, args: LocationExplorerArgs, library| async move { - let Library { db, .. } = &library; - - let location = find_location(&library, args.location_id) - .exec() - .await? - .ok_or(LocationError::IdNotFound(args.location_id))?; + R.with2(library()) + .query(|(_, library), args: LocationExplorerArgs| async move { + let Library { db, .. } = &library; - let directory_id = if let Some(mut path) = args.path { - if !path.ends_with(MAIN_SEPARATOR) { - path += MAIN_SEPARATOR_STR; - } - - Some( - db.file_path() - .find_first(vec![ - file_path::location_id::equals(location.id), - file_path::materialized_path::equals(path), - file_path::is_dir::equals(true), - ]) - .select(file_path::select!({ id })) - .exec() - .await? - .ok_or_else(|| { - rspc::Error::new(ErrorCode::NotFound, "Directory not found".into()) - })? - .id, - ) - } else { - None - }; + let location = find_location(&library, args.location_id) + .exec() + .await? + .ok_or(LocationError::IdNotFound(args.location_id))?; - let expected_kinds = args - .kind - .map(|kinds| kinds.into_iter().collect::>()) - .unwrap_or_default(); + let directory_id = if let Some(mut path) = args.path { + if !path.ends_with(MAIN_SEPARATOR) { + path += MAIN_SEPARATOR_STR; + } - let mut file_paths = db - .file_path() - .find_many(if directory_id.is_some() { - vec![ - file_path::location_id::equals(location.id), - file_path::parent_id::equals(directory_id), - ] + Some( + db.file_path() + .find_first(vec![ + file_path::location_id::equals(location.id), + file_path::materialized_path::equals(path), + file_path::is_dir::equals(true), + ]) + .select(file_path::select!({ id })) + .exec() + .await? + .ok_or_else(|| { + rspc::Error::new( + ErrorCode::NotFound, + "Directory not found".into(), + ) + })? + .id, + ) } else { - vec![file_path::location_id::equals(location.id)] - }) - .include(file_path_with_object::include()) - .exec() - .await?; + None + }; + + let expected_kinds = args + .kind + .map(|kinds| kinds.into_iter().collect::>()) + .unwrap_or_default(); - if !expected_kinds.is_empty() { - file_paths = file_paths - .into_iter() - .filter(|file_path| { - if let Some(ref object) = file_path.object { - expected_kinds.contains(&object.kind) - } else { - false - } + let mut file_paths = db + .file_path() + .find_many(if directory_id.is_some() { + vec![ + file_path::location_id::equals(location.id), + file_path::parent_id::equals(directory_id), + ] + } else { + vec![file_path::location_id::equals(location.id)] }) - .collect::>(); - } + .include(file_path_with_object::include()) + .exec() + .await?; - let mut items = Vec::with_capacity(file_paths.len()); - for file_path in file_paths { - let has_thumbnail = if let Some(cas_id) = &file_path.cas_id { - library - .thumbnail_exists(cas_id) - .await - .map_err(LocationError::IOError)? - } else { - false - }; + if !expected_kinds.is_empty() { + file_paths = file_paths + .into_iter() + .filter(|file_path| { + if let Some(ref object) = file_path.object { + expected_kinds.contains(&object.kind) + } else { + false + } + }) + .collect::>(); + } - items.push(ExplorerItem::Path { - has_thumbnail, - item: file_path, - }); - } + let mut items = Vec::with_capacity(file_paths.len()); + for file_path in file_paths { + let has_thumbnail = if let Some(cas_id) = &file_path.cas_id { + library + .thumbnail_exists(cas_id) + .await + .map_err(LocationError::IOError)? + } else { + false + }; + + items.push(ExplorerItem::Path { + has_thumbnail, + item: file_path, + }); + } - Ok(ExplorerData { - context: ExplorerContext::Location(location), - items, + Ok(ExplorerData { + context: ExplorerContext::Location(location), + items, + }) }) - }) }) - .library_mutation("create", |t| { - t(|_, args: LocationCreateArgs, library| async move { - let location = args.create(&library).await?; - scan_location(&library, location).await?; - Ok(()) - }) + .procedure("create", { + R.with2(library()) + .mutation(|(_, library), args: LocationCreateArgs| async move { + let location = args.create(&library).await?; + scan_location(&library, location).await?; + Ok(()) + }) }) - .library_mutation("update", |t| { - t(|_, args: LocationUpdateArgs, library| async move { - args.update(&library).await.map_err(Into::into) - }) + .procedure("update", { + R.with2(library()) + .mutation(|(_, library), args: LocationUpdateArgs| async move { + args.update(&library).await.map_err(Into::into) + }) }) - .library_mutation("delete", |t| { - t(|_, location_id: i32, library| async move { - delete_location(&library, location_id) - .await - .map_err(Into::into) - }) + .procedure("delete", { + R.with2(library()) + .mutation(|(_, library), location_id: i32| async move { + delete_location(&library, location_id) + .await + .map_err(Into::into) + }) }) - .library_mutation("relink", |t| { - t(|_, location_path: PathBuf, library| async move { - relink_location(&library, location_path) - .await - .map_err(Into::into) - }) + .procedure("relink", { + R.with2(library()) + .mutation(|(_, library), location_path: PathBuf| async move { + relink_location(&library, location_path) + .await + .map_err(Into::into) + }) }) - .library_mutation("addLibrary", |t| { - t(|_, args: LocationCreateArgs, library| async move { - let location = args.add_library(&library).await?; - scan_location(&library, location).await?; - Ok(()) - }) + .procedure("addLibrary", { + R.with2(library()) + .mutation(|(_, library), args: LocationCreateArgs| async move { + let location = args.add_library(&library).await?; + scan_location(&library, location).await?; + Ok(()) + }) }) - .library_mutation("fullRescan", |t| { - t(|_, location_id: i32, library| async move { - // rescan location - scan_location( - &library, - find_location(&library, location_id) - .include(location_with_indexer_rules::include()) - .exec() - .await? - .ok_or(LocationError::IdNotFound(location_id))?, - ) - .await - .map_err(Into::into) - }) + .procedure("fullRescan", { + R.with2(library()) + .mutation(|(_, library), location_id: i32| async move { + // rescan location + scan_location( + &library, + find_location(&library, location_id) + .include(location_with_indexer_rules::include()) + .exec() + .await? + .ok_or(LocationError::IdNotFound(location_id))?, + ) + .await + .map_err(Into::into) + }) }) - .library_mutation("quickRescan", |t| { + .procedure("quickRescan", { #[derive(Clone, Serialize, Deserialize, Type, Debug)] pub struct LightScanArgs { pub location_id: i32, pub sub_path: String, } - t(|_, args: LightScanArgs, library| async move { - // light rescan location - light_scan_location( - &library, - find_location(&library, args.location_id) - .include(location_with_indexer_rules::include()) - .exec() - .await? - .ok_or(LocationError::IdNotFound(args.location_id))?, - &args.sub_path, - ) - .await - .map_err(Into::into) - }) + R.with2(library()) + .mutation(|(_, library), args: LightScanArgs| async move { + // light rescan location + light_scan_location( + &library, + find_location(&library, args.location_id) + .include(location_with_indexer_rules::include()) + .exec() + .await? + .ok_or(LocationError::IdNotFound(args.location_id))?, + &args.sub_path, + ) + .await + .map_err(Into::into) + }) }) - .subscription("online", |t| { - t(|ctx, _: ()| { - let location_manager = ctx.library_manager.node_context.location_manager.clone(); + .procedure( + "online", + R.with2(library()) + .subscription(|(ctx, _), _: ()| async move { + let location_manager = + ctx.library_manager.node_context.location_manager.clone(); - let mut rx = location_manager.online_rx(); + let mut rx = location_manager.online_rx(); - async_stream::stream! { - let online = location_manager.get_online().await; + async_stream::stream! { + let online = location_manager.get_online().await; - yield online; + yield online; - while let Ok(locations) = rx.recv().await { - yield locations; + while let Ok(locations) = rx.recv().await { + yield locations; + } } - } - }) - }) + }), + ) .merge("indexer_rules.", mount_indexer_rule_routes()) } -fn mount_indexer_rule_routes() -> RouterBuilder { - ::new() - .library_mutation("create", |t| { - t(|_, args: IndexerRuleCreateArgs, library| async move { - args.create(&library).await.map_err(Into::into) - }) +fn mount_indexer_rule_routes() -> AlphaRouter { + R.router() + .procedure("create", { + R.with2(library()) + .mutation(|(_, library), args: IndexerRuleCreateArgs| async move { + args.create(&library).await.map_err(Into::into) + }) }) - .library_mutation("delete", |t| { - t(|_, indexer_rule_id: i32, library| async move { - let indexer_rule_db = library.db.indexer_rule(); + .procedure("delete", { + R.with2(library()) + .mutation(|(_, library), indexer_rule_id: i32| async move { + let indexer_rule_db = library.db.indexer_rule(); - if let Some(indexer_rule) = indexer_rule_db - .to_owned() - .find_unique(indexer_rule::id::equals(indexer_rule_id)) - .exec() - .await? - { - if indexer_rule.default { + if let Some(indexer_rule) = indexer_rule_db + .to_owned() + .find_unique(indexer_rule::id::equals(indexer_rule_id)) + .exec() + .await? + { + if indexer_rule.default { + return Err(rspc::Error::new( + ErrorCode::Forbidden, + format!("Indexer rule can't be deleted"), + )); + } + } else { return Err(rspc::Error::new( - ErrorCode::Forbidden, - format!("Indexer rule can't be deleted"), + ErrorCode::NotFound, + format!("Indexer rule not found"), )); } - } else { - return Err(rspc::Error::new( - ErrorCode::NotFound, - format!("Indexer rule not found"), - )); - } - library - .db - .indexer_rules_in_location() - .delete_many(vec![indexer_rules_in_location::indexer_rule_id::equals( - indexer_rule_id, - )]) - .exec() - .await?; + library + .db + .indexer_rules_in_location() + .delete_many(vec![indexer_rules_in_location::indexer_rule_id::equals( + indexer_rule_id, + )]) + .exec() + .await?; - indexer_rule_db - .delete(indexer_rule::id::equals(indexer_rule_id)) - .exec() - .await?; + indexer_rule_db + .delete(indexer_rule::id::equals(indexer_rule_id)) + .exec() + .await?; - Ok(()) - }) + Ok(()) + }) }) - .library_query("get", |t| { - t(|_, indexer_rule_id: i32, library| async move { - library - .db - .indexer_rule() - .find_unique(indexer_rule::id::equals(indexer_rule_id)) - .exec() - .await? - .ok_or_else(|| { - rspc::Error::new( - ErrorCode::NotFound, - format!("Indexer rule not found"), - ) - }) - }) + .procedure("get", { + R.with2(library()) + .query(|(_, library), indexer_rule_id: i32| async move { + library + .db + .indexer_rule() + .find_unique(indexer_rule::id::equals(indexer_rule_id)) + .exec() + .await? + .ok_or_else(|| { + rspc::Error::new( + ErrorCode::NotFound, + format!("Indexer rule not found"), + ) + }) + }) }) - .library_query("list", |t| { - t(|_, _: (), library| async move { + .procedure("list", { + R.with2(library()).query(|(_, library), _: ()| async move { library .db .indexer_rule() @@ -336,17 +354,18 @@ fn mount_indexer_rule_routes() -> RouterBuilder { }) }) // list indexer rules for location, returning the indexer rule - .library_query("listForLocation", |t| { - t(|_, location_id: i32, library| async move { - library - .db - .indexer_rule() - .find_many(vec![indexer_rule::locations::some(vec![ - indexer_rules_in_location::location_id::equals(location_id), - ])]) - .exec() - .await - .map_err(Into::into) - }) + .procedure("listForLocation", { + R.with2(library()) + .query(|(_, library), location_id: i32| async move { + library + .db + .indexer_rule() + .find_many(vec![indexer_rule::locations::some(vec![ + indexer_rules_in_location::location_id::equals(location_id), + ])]) + .exec() + .await + .map_err(Into::into) + }) }) } diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs index 5f9342c6af93..39aed8c8e965 100644 --- a/core/src/api/mod.rs +++ b/core/src/api/mod.rs @@ -1,12 +1,15 @@ use chrono::{DateTime, Utc}; use prisma_client_rust::{operator::or, Direction}; -use rspc::Config; +use rspc::{alpha::Rspc, Config}; use serde::{Deserialize, Serialize}; use specta::Type; use std::sync::Arc; use crate::{ - api::locations::{file_path_with_object, ExplorerItem}, + api::{ + locations::{file_path_with_object, ExplorerItem}, + utils::library, + }, location::LocationError, node::NodeConfig, prisma::*, @@ -15,11 +18,11 @@ use crate::{ use utils::{InvalidRequests, InvalidateOperationEvent}; -use self::utils::LibraryRequest; +#[allow(non_upper_case_globals)] +pub(self) const R: Rspc = Rspc::new(); pub type Ctx = Arc; pub type Router = rspc::Router; -pub(crate) type RouterBuilder = rspc::RouterBuilder; /// Represents an internal core event, these are exposed to client via a rspc subscription. #[derive(Debug, Clone, Serialize, Type)] @@ -55,22 +58,22 @@ pub(crate) fn mount() -> Arc { std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../packages/client/src/core.ts"), ); - let r = ::new() - .config(config) - .query("buildInfo", |t| { + let r = R + .router() + .procedure("buildInfo", { #[derive(Serialize, Type)] pub struct BuildInfo { version: &'static str, commit: &'static str, } - t(|_, _: ()| BuildInfo { + R.query(|_, _: ()| BuildInfo { version: env!("CARGO_PKG_VERSION"), commit: env!("GIT_HASH"), }) }) - .query("nodeState", |t| { - t(|ctx, _: ()| async move { + .procedure("nodeState", { + R.query(|ctx, _: ()| async move { Ok(NodeState { config: ctx.config.get().await, // We are taking the assumption here that this value is only used on the frontend for display purposes @@ -83,14 +86,13 @@ pub(crate) fn mount() -> Arc { }) }) }) - .library_query("search", |t| { + .procedure("search", { #[derive(Deserialize, Type, Debug, Clone, Copy)] #[serde(rename_all = "camelCase")] #[specta(inline)] enum Ordering { Name(bool), } - impl Ordering { fn get_direction(&self) -> Direction { match self { @@ -99,18 +101,14 @@ pub(crate) fn mount() -> Arc { .then_some(Direction::Asc) .unwrap_or(Direction::Desc) } - fn to_param(self) -> file_path::OrderByParam { let dir = self.get_direction(); - use file_path::*; - match self { Self::Name(_) => name::order(dir), } } } - #[derive(Deserialize, Type, Debug)] #[serde(rename_all = "camelCase")] #[specta(inline)] @@ -140,81 +138,74 @@ pub(crate) fn mount() -> Arc { path: Option, } - t(|_, args: Args, library| async move { - let params = args - .search - .map(|search| { - search - .split(' ') - .map(str::to_string) - .map(file_path::materialized_path::contains) - .map(Some) - .collect::>() - }) - .unwrap_or_default() - .into_iter() - .chain([ - args.location_id.map(file_path::location_id::equals), - args.kind - .map(|kind| file_path::object::is(vec![object::kind::equals(kind)])), - args.extension.map(file_path::extension::equals), - (!args.tags.is_empty()).then(|| { - file_path::object::is(vec![object::tags::some(vec![ - tag_on_object::tag::is(vec![or(args - .tags - .into_iter() - .map(tag::id::equals) - .collect())]), - ])]) - }), - args.created_at_from - .map(|v| file_path::date_created::gte(v.into())), - args.created_at_to - .map(|v| file_path::date_created::lte(v.into())), - args.path.map(file_path::materialized_path::starts_with), - ]) - .flatten() - .collect(); - - let mut query = library.db.file_path().find_many(params); - - if let Some((loc_id, file_id)) = args.after_file_id { - query = query.cursor(file_path::location_id_id(loc_id, file_id)) - } - - if let Some(order) = args.order { - query = query.order_by(order.to_param()); - } - - if let Some(take) = args.take { - query = query.take(take as i64); - } - - let file_paths = query - .include(file_path_with_object::include()) - .exec() - .await?; - - let mut items = Vec::with_capacity(file_paths.len()); - - for file_path in file_paths { - let has_thumbnail = if let Some(cas_id) = &file_path.cas_id { - library - .thumbnail_exists(cas_id) - .await - .map_err(LocationError::IOError)? - } else { - false - }; - - items.push(ExplorerItem::Path { - has_thumbnail, - item: file_path, - }) - } - - Ok(items) - }) + R.with2(library()) + .query(|(_, library), args: Args| async move { + let params = args + .search + .map(|search| { + search + .split(' ') + .map(str::to_string) + .map(file_path::materialized_path::contains) + .map(Some) + .collect::>() + }) + .unwrap_or_default() + .into_iter() + .chain([ + args.location_id.map(file_path::location_id::equals), + args.kind.map(|kind| { + file_path::object::is(vec![object::kind::equals(kind)]) + }), + args.extension.map(file_path::extension::equals), + (!args.tags.is_empty()).then(|| { + file_path::object::is(vec![object::tags::some(vec![ + tag_on_object::tag::is(vec![or(args + .tags + .into_iter() + .map(tag::id::equals) + .collect())]), + ])]) + }), + args.created_at_from + .map(|v| file_path::date_created::gte(v.into())), + args.created_at_to + .map(|v| file_path::date_created::lte(v.into())), + args.path.map(file_path::materialized_path::starts_with), + ]) + .flatten() + .collect(); + let mut query = library.db.file_path().find_many(params); + if let Some((loc_id, file_id)) = args.after_file_id { + query = query.cursor(file_path::location_id_id(loc_id, file_id)) + } + if let Some(order) = args.order { + query = query.order_by(order.to_param()); + } + if let Some(take) = args.take { + query = query.take(take as i64); + } + let file_paths = query + .include(file_path_with_object::include()) + .exec() + .await?; + let mut items = Vec::with_capacity(file_paths.len()); + for file_path in file_paths { + let has_thumbnail = if let Some(cas_id) = &file_path.cas_id { + library + .thumbnail_exists(cas_id) + .await + .map_err(LocationError::IOError)? + } else { + false + }; + items.push(ExplorerItem::Path { + has_thumbnail, + item: file_path, + }) + } + Ok(items) + }) }) .merge("library.", libraries::mount()) .merge("volumes.", volumes::mount()) @@ -226,7 +217,7 @@ pub(crate) fn mount() -> Arc { .merge("p2p.", p2p::mount()) .merge("sync.", sync::mount()) .merge("invalidation.", utils::mount_invalidate()) - .build() + .compat_with_config(config) .arced(); InvalidRequests::validate(r.clone()); // This validates all invalidation calls. diff --git a/core/src/api/p2p.rs b/core/src/api/p2p.rs index b9dc0dbb05fa..7397b3310ea3 100644 --- a/core/src/api/p2p.rs +++ b/core/src/api/p2p.rs @@ -1,3 +1,4 @@ +use rspc::alpha::AlphaRouter; use sd_p2p::PeerId; use serde::Deserialize; use specta::Type; @@ -5,12 +6,12 @@ use std::path::PathBuf; use crate::p2p::P2PEvent; -use super::RouterBuilder; +use super::{Ctx, R}; -pub(crate) fn mount() -> RouterBuilder { - RouterBuilder::new() - .subscription("events", |t| { - t(|ctx, _: ()| { +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("events", { + R.subscription(|ctx, _: ()| async move { let mut rx = ctx.p2p.subscribe(); async_stream::stream! { // TODO: Don't block subscription start @@ -33,14 +34,14 @@ pub(crate) fn mount() -> RouterBuilder { } }) }) - .mutation("spacedrop", |t| { + .procedure("spacedrop", { #[derive(Type, Deserialize)] pub struct SpacedropArgs { peer_id: PeerId, file_path: String, } - t(|ctx, args: SpacedropArgs| async move { + R.mutation(|ctx, args: SpacedropArgs| async move { ctx.p2p .big_bad_spacedrop(args.peer_id, PathBuf::from(args.file_path)) .await; diff --git a/core/src/api/sync.rs b/core/src/api/sync.rs index c3f2fb9d8359..ccf63667b6f9 100644 --- a/core/src/api/sync.rs +++ b/core/src/api/sync.rs @@ -1,24 +1,28 @@ +use rspc::alpha::AlphaRouter; + use crate::sync::SyncMessage; -use super::{utils::LibraryRequest, RouterBuilder}; +use super::{utils::library, Ctx, R}; -pub fn mount() -> RouterBuilder { - RouterBuilder::new() - .library_subscription("newMessage", |t| { - t(|_, _: (), library| { - async_stream::stream! { - let mut rx = library.sync.tx.subscribe(); - while let Ok(msg) = rx.recv().await { - let op = match msg { - SyncMessage::Ingested(op) => op, - SyncMessage::Created(op) => op - }; - yield op; +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("newMessage", { + R.with2(library()) + .subscription(|(_, library), _: ()| async move { + async_stream::stream! { + let mut rx = library.sync.tx.subscribe(); + while let Ok(msg) = rx.recv().await { + let op = match msg { + SyncMessage::Ingested(op) => op, + SyncMessage::Created(op) => op + }; + yield op; + } } - } - }) + }) }) - .library_query("messages", |t| { - t(|_, _: (), library| async move { Ok(library.sync.get_ops().await?) }) + .procedure("messages", { + R.with2(library()) + .query(|(_, library), _: ()| async move { Ok(library.sync.get_ops().await?) }) }) } diff --git a/core/src/api/tags.rs b/core/src/api/tags.rs index aa0989943001..e1ab3adee7eb 100644 --- a/core/src/api/tags.rs +++ b/core/src/api/tags.rs @@ -1,4 +1,4 @@ -use rspc::ErrorCode; +use rspc::{alpha::AlphaRouter, ErrorCode}; use serde::Deserialize; use specta::Type; @@ -14,98 +14,101 @@ use crate::{ sync, }; -use super::{utils::LibraryRequest, RouterBuilder}; +use super::{utils::library, Ctx, R}; -pub(crate) fn mount() -> RouterBuilder { - RouterBuilder::new() - .library_query("list", |t| { - t( - |_, _: (), library| async move { Ok(library.db.tag().find_many(vec![]).exec().await?) }, - ) +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("list", { + R.with2(library()).query(|(_, library), _: ()| async move { + Ok(library.db.tag().find_many(vec![]).exec().await?) + }) }) - .library_query("getExplorerData", |t| { - t(|_, tag_id: i32, library| async move { - info!("Getting files for tag {}", tag_id); + .procedure("getExplorerData", { + R.with2(library()) + .query(|(_, library), tag_id: i32| async move { + info!("Getting files for tag {}", tag_id); - let Library { db, .. } = &library; + let Library { db, .. } = &library; - let tag = db - .tag() - .find_unique(tag::id::equals(tag_id)) - .exec() - .await? - .ok_or_else(|| { - rspc::Error::new( - ErrorCode::NotFound, - format!("Tag not found"), - ) - })?; + let tag = db + .tag() + .find_unique(tag::id::equals(tag_id)) + .exec() + .await? + .ok_or_else(|| { + rspc::Error::new( + ErrorCode::NotFound, + format!("Tag not found"), + ) + })?; - let objects = db - .object() - .find_many(vec![object::tags::some(vec![ - tag_on_object::tag_id::equals(tag_id), - ])]) - .include(object_with_file_paths::include()) - .exec() - .await?; + let objects = db + .object() + .find_many(vec![object::tags::some(vec![ + tag_on_object::tag_id::equals(tag_id), + ])]) + .include(object_with_file_paths::include()) + .exec() + .await?; - let mut items = Vec::with_capacity(objects.len()); + let mut items = Vec::with_capacity(objects.len()); - for object in objects { - let cas_id = object - .file_paths - .iter() - .map(|fp| fp.cas_id.as_ref()) - .find_map(|c| c); + for object in objects { + let cas_id = object + .file_paths + .iter() + .map(|fp| fp.cas_id.as_ref()) + .find_map(|c| c); - let has_thumbnail = if let Some(cas_id) = cas_id { - library.thumbnail_exists(cas_id).await.map_err(|e| { - rspc::Error::with_cause( - ErrorCode::InternalServerError, - "Failed to check that thumbnail exists".to_string(), - e, - ) - })? - } else { - false - }; + let has_thumbnail = if let Some(cas_id) = cas_id { + library.thumbnail_exists(cas_id).await.map_err(|e| { + rspc::Error::with_cause( + ErrorCode::InternalServerError, + "Failed to check that thumbnail exists".to_string(), + e, + ) + })? + } else { + false + }; - items.push(ExplorerItem::Object { - has_thumbnail, - item: object, - }); - } + items.push(ExplorerItem::Object { + has_thumbnail, + item: object, + }); + } - info!("Got objects {}", items.len()); + info!("Got objects {}", items.len()); - Ok(ExplorerData { - context: ExplorerContext::Tag(tag), - items, + Ok(ExplorerData { + context: ExplorerContext::Tag(tag), + items, + }) }) - }) }) - .library_query("getForObject", |t| { - t(|_, object_id: i32, library| async move { - Ok(library - .db - .tag() - .find_many(vec![tag::tag_objects::some(vec![ - tag_on_object::object_id::equals(object_id), - ])]) - .exec() - .await?) - }) + .procedure("getForObject", { + R.with2(library()) + .query(|(_, library), object_id: i32| async move { + Ok(library + .db + .tag() + .find_many(vec![tag::tag_objects::some(vec![ + tag_on_object::object_id::equals(object_id), + ])]) + .exec() + .await?) + }) }) - .library_query("get", |t| { - t(|_, tag_id: i32, library| async move { - Ok(library - .db - .tag() - .find_unique(tag::id::equals(tag_id)) - .exec() - .await?) - }) + .procedure("get", { + R.with2(library()) + .query(|(_, library), tag_id: i32| async move { + Ok(library + .db + .tag() + .find_unique(tag::id::equals(tag_id)) + .exec() + .await?) + }) }) // .library_mutation("create", |t| { // #[derive(Type, Deserialize)] @@ -120,43 +123,44 @@ pub(crate) fn mount() -> RouterBuilder { // Ok(created_tag) // }) // }) - .library_mutation("create", |t| { + .procedure("create", { #[derive(Type, Deserialize)] pub struct TagCreateArgs { pub name: String, pub color: String, } - t(|_, args: TagCreateArgs, library| async move { - let Library { db, sync, .. } = &library; + R.with2(library()) + .mutation(|(_, library), args: TagCreateArgs| async move { + let Library { db, sync, .. } = &library; - let pub_id = Uuid::new_v4().as_bytes().to_vec(); + let pub_id = Uuid::new_v4().as_bytes().to_vec(); - let created_tag = sync - .write_op( - db, - sync.unique_shared_create( - sync::tag::SyncId { - pub_id: pub_id.clone(), - }, - [("name", json!(args.name)), ("color", json!(args.color))], - ), - db.tag().create( - pub_id, - vec![ - tag::name::set(Some(args.name)), - tag::color::set(Some(args.color)), - ], - ), - ) - .await?; + let created_tag = sync + .write_op( + db, + sync.unique_shared_create( + sync::tag::SyncId { + pub_id: pub_id.clone(), + }, + [("name", json!(args.name)), ("color", json!(args.color))], + ), + db.tag().create( + pub_id, + vec![ + tag::name::set(Some(args.name)), + tag::color::set(Some(args.color)), + ], + ), + ) + .await?; - invalidate_query!(library, "tags.list"); + invalidate_query!(library, "tags.list"); - Ok(created_tag) - }) + Ok(created_tag) + }) }) - .library_mutation("assign", |t| { + .procedure("assign", { #[derive(Debug, Type, Deserialize)] pub struct TagAssignArgs { pub object_id: i32, @@ -164,33 +168,34 @@ pub(crate) fn mount() -> RouterBuilder { pub unassign: bool, } - t(|_, args: TagAssignArgs, library| async move { - if args.unassign { - library - .db - .tag_on_object() - .delete(tag_on_object::tag_id_object_id(args.tag_id, args.object_id)) - .exec() - .await?; - } else { - library - .db - .tag_on_object() - .create( - tag::id::equals(args.tag_id), - object::id::equals(args.object_id), - vec![], - ) - .exec() - .await?; - } + R.with2(library()) + .mutation(|(_, library), args: TagAssignArgs| async move { + if args.unassign { + library + .db + .tag_on_object() + .delete(tag_on_object::tag_id_object_id(args.tag_id, args.object_id)) + .exec() + .await?; + } else { + library + .db + .tag_on_object() + .create( + tag::id::equals(args.tag_id), + object::id::equals(args.object_id), + vec![], + ) + .exec() + .await?; + } - invalidate_query!(library, "tags.getForObject"); + invalidate_query!(library, "tags.getForObject"); - Ok(()) - }) + Ok(()) + }) }) - .library_mutation("update", |t| { + .procedure("update", { #[derive(Type, Deserialize)] pub struct TagUpdateArgs { pub id: i32, @@ -198,61 +203,64 @@ pub(crate) fn mount() -> RouterBuilder { pub color: Option, } - t(|_, args: TagUpdateArgs, library| async move { - let Library { sync, db, .. } = &library; + R.with2(library()) + .mutation(|(_, library), args: TagUpdateArgs| async move { + let Library { sync, db, .. } = &library; - let tag = db - .tag() - .find_unique(tag::id::equals(args.id)) - .select(tag::select!({ pub_id })) - .exec() - .await? - .unwrap(); + let tag = db + .tag() + .find_unique(tag::id::equals(args.id)) + .select(tag::select!({ pub_id })) + .exec() + .await? + .unwrap(); - sync.write_ops( - db, - ( - [ - args.name.as_ref().map(|v| ("name", json!(v))), - args.color.as_ref().map(|v| ("color", json!(v))), - ] - .into_iter() - .flatten() - .map(|(k, v)| { - sync.shared_update( - sync::tag::SyncId { - pub_id: tag.pub_id.clone(), - }, - k, - v, - ) - }) - .collect(), - db.tag().update( - tag::id::equals(args.id), - vec![tag::name::set(args.name), tag::color::set(args.color)], + sync.write_ops( + db, + ( + [ + args.name.as_ref().map(|v| ("name", json!(v))), + args.color.as_ref().map(|v| ("color", json!(v))), + ] + .into_iter() + .flatten() + .map(|(k, v)| { + sync.shared_update( + sync::tag::SyncId { + pub_id: tag.pub_id.clone(), + }, + k, + v, + ) + }) + .collect(), + db.tag().update( + tag::id::equals(args.id), + vec![tag::name::set(args.name), tag::color::set(args.color)], + ), ), - ), - ) - .await?; + ) + .await?; - invalidate_query!(library, "tags.list"); + invalidate_query!(library, "tags.list"); - Ok(()) - }) + Ok(()) + }) }) - .library_mutation("delete", |t| { - t(|_, tag_id: i32, library| async move { - library - .db - .tag() - .delete(tag::id::equals(tag_id)) - .exec() - .await?; + .procedure( + "delete", + R.with2(library()) + .mutation(|(_, library), tag_id: i32| async move { + library + .db + .tag() + .delete(tag::id::equals(tag_id)) + .exec() + .await?; - invalidate_query!(library, "tags.list"); + invalidate_query!(library, "tags.list"); - Ok(()) - }) - }) + Ok(()) + }), + ) } diff --git a/core/src/api/utils/invalidate.rs b/core/src/api/utils/invalidate.rs index 902e160f7886..30b709c7a1dc 100644 --- a/core/src/api/utils/invalidate.rs +++ b/core/src/api/utils/invalidate.rs @@ -1,6 +1,7 @@ -use crate::api::{CoreEvent, Router, RouterBuilder}; +use crate::api::{CoreEvent, Ctx, Router, R}; use async_stream::stream; +use rspc::alpha::AlphaRouter; use serde::Serialize; use serde_hashkey::to_key; use serde_json::Value; @@ -216,13 +217,13 @@ macro_rules! invalidate_query { }}; } -pub fn mount_invalidate() -> RouterBuilder { +pub(crate) fn mount_invalidate() -> AlphaRouter { let (tx, _) = broadcast::channel(100); let manager_thread_active = Arc::new(AtomicBool::new(false)); // TODO: Scope the invalidate queries to a specific library (filtered server side) - RouterBuilder::new().subscription("listen", move |t| { - t(move |ctx, _: ()| { + R.router().procedure("listen", { + R.subscription(move |ctx, _: ()| { // This thread is used to deal with batching and deduplication. // Their is only ever one of these management threads per Node but we spawn it like this so we can steal the event bus from the rspc context. // Batching is important because when refetching data on the frontend rspc can fetch all invalidated queries in a single round trip. diff --git a/core/src/api/utils/library.rs b/core/src/api/utils/library.rs index cd6c62519345..a580bc5b942f 100644 --- a/core/src/api/utils/library.rs +++ b/core/src/api/utils/library.rs @@ -1,11 +1,9 @@ -use std::{borrow::Cow, marker::PhantomData, panic::Location, process, sync::Arc}; - use rspc::{ - internal::{ - BuiltProcedureBuilder, LayerResult, MiddlewareBuilderLike, ResolverLayer, - UnbuiltProcedureBuilder, + alpha::{ + unstable::{MwArgMapper, MwArgMapperMiddleware}, + MwV3, }, - is_invalid_procedure_name, typedef, ErrorCode, ExecError, RequestLayer, StreamRequestLayer, + ErrorCode, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use specta::Type; @@ -15,216 +13,33 @@ use crate::{api::Ctx, library::Library}; /// Can wrap a query argument to require it to contain a `library_id` and provide helpers for working with libraries. #[derive(Clone, Serialize, Deserialize, Type)] -pub(crate) struct LibraryArgs { - pub library_id: Uuid, - pub arg: T, -} - -// WARNING: This is system is using internal API's which means it will break between rspc release. I would avoid copying it unless you understand the cost of maintaining it! -pub trait LibraryRequest { - fn library_query( - self, - key: &'static str, - builder: impl FnOnce( - UnbuiltProcedureBuilder, - ) -> BuiltProcedureBuilder, - ) -> Self - where - TArg: DeserializeOwned + Type + Send + 'static, - TResult: RequestLayer + Send, - TResolver: Fn(Ctx, TArg, Library) -> TResult + Send + Sync + 'static; - - fn library_mutation( - self, - key: &'static str, - builder: impl FnOnce( - UnbuiltProcedureBuilder, - ) -> BuiltProcedureBuilder, - ) -> Self - where - TArg: DeserializeOwned + Type + Send + 'static, - TResult: RequestLayer + Send, - TResolver: Fn(Ctx, TArg, Library) -> TResult + Send + Sync + 'static; - - fn library_subscription( - self, - key: &'static str, - builder: impl FnOnce(UnbuiltProcedureBuilder) -> BuiltProcedureBuilder, - ) -> Self - where - F: Fn(Ctx, TArg, Library) -> TResult + Send + Sync + 'static, - TArg: DeserializeOwned + Type + Send + 'static, - TResult: StreamRequestLayer + Send; -} - -// Note: This will break with middleware context switching but that's fine for now -impl LibraryRequest for rspc::RouterBuilder -where - TMiddleware: MiddlewareBuilderLike + Send + 'static, -{ - fn library_query( - mut self, - key: &'static str, - builder: impl FnOnce( - UnbuiltProcedureBuilder, - ) -> BuiltProcedureBuilder, - ) -> Self - where - TArg: DeserializeOwned + Type + Send + 'static, - TResult: RequestLayer + Send, - TResolver: Fn(Ctx, TArg, Library) -> TResult + Send + Sync + 'static, - { - if is_invalid_procedure_name(key) { - eprintln!( - "{}: rspc error: attempted to attach a query with the key '{}', however this name is not allowed. ", - Location::caller(), - key - ); - process::exit(1); - } - - let resolver = Arc::new(builder(UnbuiltProcedureBuilder::default()).resolver); - let ty = - typedef::, TResult::Result>(Cow::Borrowed(key), self.typ_store()) - .unwrap(); - let layer = self.prev_middleware().build(ResolverLayer { - func: move |ctx: Ctx, input, _| { - let resolver = resolver.clone(); - Ok(LayerResult::FutureValueOrStream(Box::pin(async move { - let args: LibraryArgs = - serde_json::from_value(input).map_err(ExecError::DeserializingArgErr)?; - - let library = ctx - .library_manager - .get_ctx(args.library_id) - .await - .ok_or_else(|| { - rspc::Error::new( - ErrorCode::BadRequest, - "You must specify a valid library to use this operation." - .to_string(), - ) - })?; +pub(crate) struct LibraryArgs(pub Uuid, pub T); - resolver(ctx, args.arg, library) - .into_layer_result()? - .into_value_or_stream() - .await - }))) - }, - phantom: PhantomData, - }); - self.queries().append(key.into(), layer, ty); - self - } - - fn library_mutation( - mut self, - key: &'static str, - builder: impl FnOnce( - UnbuiltProcedureBuilder, - ) -> BuiltProcedureBuilder, - ) -> Self - where - TArg: DeserializeOwned + Type + Send + 'static, - TResult: RequestLayer + Send, - TResolver: Fn(Ctx, TArg, Library) -> TResult + Send + Sync + 'static, - { - if is_invalid_procedure_name(key) { - eprintln!( - "{}: rspc error: attempted to attach a mutation with the key '{}', however this name is not allowed. ", - Location::caller(), - key - ); - process::exit(1); - } - - let resolver = Arc::new(builder(UnbuiltProcedureBuilder::default()).resolver); - let ty = - typedef::, TResult::Result>(Cow::Borrowed(key), self.typ_store()) - .unwrap(); - let layer = self.prev_middleware().build(ResolverLayer { - func: move |ctx: Ctx, input, _| { - let resolver = resolver.clone(); - Ok(LayerResult::FutureValueOrStream(Box::pin(async move { - let args: LibraryArgs = - serde_json::from_value(input).map_err(ExecError::DeserializingArgErr)?; +pub(crate) struct LibraryArgsLike; +impl MwArgMapper for LibraryArgsLike { + type Input = LibraryArgs where T: Type + DeserializeOwned + 'static; + type State = Uuid; - let library = ctx - .library_manager - .get_ctx(args.library_id) - .await - .ok_or_else(|| { - rspc::Error::new( - ErrorCode::BadRequest, - "You must specify a valid library to use this operation." - .to_string(), - ) - })?; - - resolver(ctx, args.arg, library) - .into_layer_result()? - .into_value_or_stream() - .await - }))) - }, - phantom: PhantomData, - }); - self.mutations().append(key.into(), layer, ty); - self + fn map( + arg: Self::Input, + ) -> (T, Self::State) { + (arg.1, arg.0) } +} - fn library_subscription( - mut self, - key: &'static str, - builder: impl FnOnce(UnbuiltProcedureBuilder) -> BuiltProcedureBuilder, - ) -> Self - where - F: Fn(Ctx, TArg, Library) -> TResult + Send + Sync + 'static, - TArg: DeserializeOwned + Type + Send + 'static, - TResult: StreamRequestLayer + Send, - { - if is_invalid_procedure_name(key) { - eprintln!( - "{}: rspc error: attempted to attach a subscription with the key '{}', however this name is not allowed. ", - Location::caller(), - key - ); - process::exit(1); - } - - let resolver = Arc::new(builder(UnbuiltProcedureBuilder::default()).resolver); - let ty = - typedef::, TResult::Result>(Cow::Borrowed(key), self.typ_store()) - .unwrap(); - let layer = self.prev_middleware().build(ResolverLayer { - func: move |ctx: Ctx, input, _| { - let resolver = resolver.clone(); - Ok(LayerResult::FutureValueOrStream(Box::pin(async move { - let args: LibraryArgs = - serde_json::from_value(input).map_err(ExecError::DeserializingArgErr)?; - - let library = ctx - .library_manager - .get_ctx(args.library_id) - .await - .ok_or_else(|| { - rspc::Error::new( - ErrorCode::BadRequest, - "You must specify a valid library to use this operation." - .to_string(), - ) - })?; - - resolver(ctx, args.arg, library) - .into_layer_result()? - .into_value_or_stream() - .await - }))) - }, - phantom: PhantomData, - }); - self.subscriptions().append(key.into(), layer, ty); - self - } +pub(crate) fn library() -> impl MwV3 { + MwArgMapperMiddleware::::new().mount(|mw, ctx: Ctx, library_id| async move { + let library = ctx + .library_manager + .get_ctx(library_id) + .await + .ok_or_else(|| { + rspc::Error::new( + ErrorCode::BadRequest, + "You must specify a valid library to use this operation.".to_string(), + ) + })?; + + Ok(mw.next((ctx, library))) + }) } diff --git a/core/src/api/utils/mod.rs b/core/src/api/utils/mod.rs index 1e29222fe406..93b00c1042f3 100644 --- a/core/src/api/utils/mod.rs +++ b/core/src/api/utils/mod.rs @@ -6,7 +6,7 @@ mod invalidate; mod library; pub use invalidate::*; -pub use library::*; +pub(crate) use library::*; /// Returns the size of the file or directory pub async fn get_size(path: impl AsRef) -> Result { diff --git a/core/src/api/volumes.rs b/core/src/api/volumes.rs index f9a9724be40b..a30b96f7b513 100644 --- a/core/src/api/volumes.rs +++ b/core/src/api/volumes.rs @@ -1,7 +1,11 @@ +use rspc::alpha::AlphaRouter; + use crate::volume::get_volumes; -use super::RouterBuilder; +use super::{Ctx, R}; -pub(crate) fn mount() -> RouterBuilder { - RouterBuilder::new().query("list", |t| t(|_, _: ()| Ok(get_volumes()?))) +pub(crate) fn mount() -> AlphaRouter { + R.router().procedure("list", { + R.query(|_, _: ()| async move { Ok(get_volumes()?) }) + }) }