From e2d1862766d9946c23697706d3fd138bd0eafbfa Mon Sep 17 00:00:00 2001 From: Arnab Chakraborty <11457760+Rocky43007@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:55:22 +0300 Subject: [PATCH] Migrate all cloud routes to the new API system All cloud routes are now migrated to the new system in the backend. The old routes are still available, as the frontend has not been modified to use the new ones yet. --- Cargo.lock | 4 +- core/src/api/cloud/libraries.rs | 164 ++++++++++++++++++ core/src/api/cloud/library.rs | 3 + core/src/api/cloud/locations.rs | 3 + core/src/api/cloud/mod.rs | 6 +- core/src/api/cloud/new_locations.rs | 89 ++++++++++ crates/cloud-api/src/lib.rs | 1 + interface/app/$libraryId/debug/cloud.tsx | 24 +-- .../settings/client/account/index.tsx | 23 +-- packages/client/src/core.ts | 42 ++++- 10 files changed, 320 insertions(+), 39 deletions(-) create mode 100644 core/src/api/cloud/libraries.rs create mode 100644 core/src/api/cloud/new_locations.rs diff --git a/Cargo.lock b/Cargo.lock index 12506b256..d1d12d3d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4379,7 +4379,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core 0.51.1", + "windows-core 0.52.0", ] [[package]] @@ -9144,7 +9144,7 @@ dependencies = [ [[package]] name = "sd-cloud-schema" version = "0.1.0" -source = "git+https://github.com/spacedriveapp/cloud-services-schema?branch=main#ff92c41e2b908732c58e77c26e0e3eec05f95549" +source = "git+https://github.com/spacedriveapp/cloud-services-schema?branch=main#dfbab0a7f19c1c3fafacb2053b1de628760d8dc8" dependencies = [ "argon2", "async-stream", diff --git a/core/src/api/cloud/libraries.rs b/core/src/api/cloud/libraries.rs new file mode 100644 index 000000000..609d24e53 --- /dev/null +++ b/core/src/api/cloud/libraries.rs @@ -0,0 +1,164 @@ +use crate::{api::utils::library, invalidate_query}; +use rspc::alpha::AlphaRouter; +use sd_cloud_schema::libraries; +use tracing::debug; +use uuid::Uuid; + +use crate::{ + api::{Ctx, R}, + try_get_cloud_services_client, +}; + +pub fn mount() -> AlphaRouter { + R.router() + .procedure("get", { + R.query(|node, req: libraries::get::Request| async move { + let libraries::get::Response(library) = super::handle_comm_error( + try_get_cloud_services_client!(node)? + .libraries() + .get(req) + .await, + "Failed to get library;", + )??; + + debug!(?library, "Got library"); + + Ok(library) + }) + }) + .procedure("list", { + R.query(|node, req: libraries::list::Request| async move { + let libraries::list::Response(libraries) = super::handle_comm_error( + try_get_cloud_services_client!(node)? + .libraries() + .list(req) + .await, + "Failed to list libraries;", + )??; + + debug!(?libraries, "Listed libraries"); + + Ok(libraries) + }) + }) + .procedure("create", { + R.with2(library()) + .mutation(|(node, library), _: ()| async move { + // let node_config = node.config.get().await; + // let cloud_library = sd_cloud_api::library::create( + // node.cloud_api_config().await, + // library.id, + // &library.config().await.name, + // library.instance_uuid, + // library.identity.to_remote_identity(), + // node_config.id, + // node_config.identity.to_remote_identity(), + // &node.p2p.peer_metadata(), + // ) + // .await?; + // node.libraries + // .edit( + // library.id, + // None, + // MaybeUndefined::Undefined, + // MaybeUndefined::Value(cloud_library.id), + // None, + // ) + // .await?; + + invalidate_query!(library, "cloud.library.get"); + // invalidate_query!(library, "cloud.library.get"); + debug!("TODO: Functionality not implemented"); + + Ok(()) + }) + }) + .procedure("join", { + R.mutation(|node, library_id: Uuid| async move { + // let Some(cloud_library) = + // sd_cloud_api::library::get(node.cloud_api_config().await, library_id).await? + // else { + // return Err(rspc::Error::new( + // rspc::ErrorCode::NotFound, + // "Library not found".to_string(), + // )); + // }; + + // let library = node + // .libraries + // .create_with_uuid( + // library_id, + // LibraryName::new(cloud_library.name).map_err(|e| { + // rspc::Error::new(rspc::ErrorCode::InternalServerError, e.to_string()) + // })?, + // None, + // false, + // None, + // &node, + // true, + // ) + // .await?; + // node.libraries + // .edit( + // library.id, + // None, + // MaybeUndefined::Undefined, + // MaybeUndefined::Value(cloud_library.id), + // None, + // ) + // .await?; + + // let node_config = node.config.get().await; + // let instances = sd_cloud_api::library::join( + // node.cloud_api_config().await, + // library_id, + // library.instance_uuid, + // library.identity.to_remote_identity(), + // node_config.id, + // node_config.identity.to_remote_identity(), + // node.p2p.peer_metadata(), + // ) + // .await?; + + // for instance in instances { + // crate::cloud::sync::receive::upsert_instance( + // library.id, + // &library.db, + // &library.sync, + // &node.libraries, + // &instance.uuid, + // instance.identity, + // &instance.node_id, + // RemoteIdentity::from_str(&instance.node_remote_identity) + // .expect("malformed remote identity in the DB"), + // instance.metadata, + // ) + // .await?; + // } + + // invalidate_query!(library, "cloud.library.get"); + // invalidate_query!(library, "cloud.library.list"); + + // Ok(LibraryConfigWrapped::from_library(&library).await) + + debug!("TODO: Functionality not implemented. Joining will be removed in the future, but for now, it's a no-op"); + + Ok(()) + }) + }) + .procedure("update", { + R.mutation(|node, req: libraries::update::Request| async move { + super::handle_comm_error( + try_get_cloud_services_client!(node)? + .libraries() + .update(req) + .await, + "Failed to update library;", + )??; + + debug!("Updated library"); + + Ok(()) + }) + }) +} diff --git a/core/src/api/cloud/library.rs b/core/src/api/cloud/library.rs index 4dc83ac94..ca110c397 100644 --- a/core/src/api/cloud/library.rs +++ b/core/src/api/cloud/library.rs @@ -1,3 +1,6 @@ +// This file is being deprecated in favor of libraries.rs +// This is due to the migration to the new API system, but the frontend is still using this file + use crate::{api::utils::library, invalidate_query}; use super::*; diff --git a/core/src/api/cloud/locations.rs b/core/src/api/cloud/locations.rs index f5ad209d6..a6c9c80cc 100644 --- a/core/src/api/cloud/locations.rs +++ b/core/src/api/cloud/locations.rs @@ -1,3 +1,6 @@ +// This file is being deprecated in favor of libraries.rs +// This is due to the migration to the new API system, but the frontend is still using this file + use crate::api::{Ctx, R}; use rspc::alpha::AlphaRouter; diff --git a/core/src/api/cloud/mod.rs b/core/src/api/cloud/mod.rs index 745a415c7..0b9ef5065 100644 --- a/core/src/api/cloud/mod.rs +++ b/core/src/api/cloud/mod.rs @@ -1,4 +1,4 @@ -use crate::{api::libraries::LibraryConfigWrapped, invalidate_query, library::LibraryName}; +// use crate::{api::libraries::LibraryConfigWrapped, invalidate_query, library::LibraryName}; use sd_cloud_schema::{auth, users}; @@ -11,6 +11,8 @@ use super::{Ctx, R}; mod devices; mod library; mod locations; +mod new_locations; +mod libraries; #[macro_export] macro_rules! try_get_cloud_services_client { @@ -29,7 +31,9 @@ macro_rules! try_get_cloud_services_client { pub(crate) fn mount() -> AlphaRouter { R.router() .merge("library.", library::mount()) + .merge("libraries.", libraries::mount()) .merge("locations.", locations::mount()) + .merge("new_locations.", new_locations::mount()) .merge("devices.", devices::mount()) .procedure("bootstrap", { R.mutation(|node, access_token: auth::AccessToken| async move { diff --git a/core/src/api/cloud/new_locations.rs b/core/src/api/cloud/new_locations.rs new file mode 100644 index 000000000..6ac9f5f37 --- /dev/null +++ b/core/src/api/cloud/new_locations.rs @@ -0,0 +1,89 @@ +use crate::{ + api::{Ctx, R}, + try_get_cloud_services_client, +}; + +use rspc::alpha::AlphaRouter; +use sd_cloud_schema::locations; +use tracing::debug; + +pub fn mount() -> AlphaRouter { + R.router() + .procedure("get", { + R.query(|node, req: locations::get::Request| async move { + let locations::get::Response(location) = super::handle_comm_error( + try_get_cloud_services_client!(node)? + .locations() + .get(req) + .await, + "Failed to get location;", + )??; + + debug!(?location, "Got location"); + + Ok(location) + }) + }) + .procedure("list", { + R.query(|node, req: locations::list::Request| async move { + let locations::list::Response(locations) = super::handle_comm_error( + try_get_cloud_services_client!(node)? + .locations() + .list(req) + .await, + "Failed to list locations;", + )??; + + debug!(?locations, "Listed locations"); + + Ok(locations) + }) + }) + .procedure("create", { + R.mutation(|node, req: locations::create::Request| async move { + super::handle_comm_error( + try_get_cloud_services_client!(node)? + .locations() + .create(req) + .await, + "Failed to create location;", + )??; + + debug!("Created location"); + + // Should we invalidate the location list cache here? + + Ok(()) + }) + }) + .procedure("delete", { + R.mutation(|node, req: locations::delete::Request| async move { + super::handle_comm_error( + try_get_cloud_services_client!(node)? + .locations() + .delete(req) + .await, + "Failed to delete location;", + )??; + + debug!("Deleted location"); + + Ok(()) + }) + }) + .procedure("update", { + R.mutation(|node, req: locations::update::Request| async move { + super::handle_comm_error( + try_get_cloud_services_client!(node)? + .locations() + .update(req) + .await, + "Failed to update location;", + )??; + + debug!("Updated location"); + + Ok(()) + }) + }) +} diff --git a/crates/cloud-api/src/lib.rs b/crates/cloud-api/src/lib.rs index a6d557dca..6880f4ab6 100644 --- a/crates/cloud-api/src/lib.rs +++ b/crates/cloud-api/src/lib.rs @@ -517,6 +517,7 @@ pub mod library { } #[derive(Type, Serialize, Deserialize)] +#[specta(rename = "Core_CloudLocation")] pub struct CloudLocation { id: String, name: String, diff --git a/interface/app/$libraryId/debug/cloud.tsx b/interface/app/$libraryId/debug/cloud.tsx index de06c1185..4f6a55527 100644 --- a/interface/app/$libraryId/debug/cloud.tsx +++ b/interface/app/$libraryId/debug/cloud.tsx @@ -2,8 +2,6 @@ import { CheckCircle, XCircle } from '@phosphor-icons/react'; import { Suspense, useMemo } from 'react'; import { auth, - CloudInstance, - CloudLibrary, HardwareModel, useBridgeQuery, useLibraryContext, @@ -51,16 +49,22 @@ export const Component = () => { // million-ignore function Authenticated() { const { library } = useLibraryContext(); - const cloudLibrary = useLibraryQuery(['cloud.library.get'], { suspense: true, retry: false }); - const cloudLibraryList = useBridgeQuery(['cloud.library.list'], { suspense: true, retry: false }); - console.log("[DEBUG] cloudLibraryList", cloudLibraryList); + const cloudLibrary: any = useLibraryQuery(['cloud.library.get'], { + suspense: true, + retry: false + }); + const cloudLibraryList = useBridgeQuery(['cloud.library.list'], { + suspense: true, + retry: false + }); + console.log('[DEBUG] cloudLibraryList', cloudLibraryList); const createLibrary = useLibraryMutation(['cloud.library.create']); const { t } = useLocale(); const thisInstance = useMemo(() => { if (!cloudLibrary.data) return undefined; return cloudLibrary.data.instances.find( - (instance) => instance.uuid === library.instance_id + (instance: any) => instance.uuid === library.instance_id ); }, [cloudLibrary.data, library.instance_id]); @@ -113,7 +117,7 @@ function Authenticated() { } // million-ignore -const Instances = ({ instances }: { instances: CloudInstance[] }) => { +const Instances = ({ instances }: { instances: any[] }) => { const { library } = useLibraryContext(); const filteredInstances = instances.filter((instance) => instance.uuid !== library.instance_id); return ( @@ -173,8 +177,8 @@ const Instances = ({ instances }: { instances: CloudInstance[] }) => { }; interface LibraryProps { - cloudLibrary: CloudLibrary; - thisInstance: CloudInstance | undefined; + cloudLibrary: any; + thisInstance: any | undefined; } // million-ignore @@ -206,7 +210,7 @@ const Library = ({ thisInstance, cloudLibrary }: LibraryProps) => { }; interface ThisInstanceProps { - instance: CloudInstance; + instance: any; } // million-ignore diff --git a/interface/app/$libraryId/settings/client/account/index.tsx b/interface/app/$libraryId/settings/client/account/index.tsx index 0985029b2..a7a6ea53c 100644 --- a/interface/app/$libraryId/settings/client/account/index.tsx +++ b/interface/app/$libraryId/settings/client/account/index.tsx @@ -86,14 +86,6 @@ function HostedLocationsPlayground() { locations.refetch(); } }); - const doTheThing = useBridgeMutation('cloud.locations.testing', { - onSuccess() { - toast.success('Uploaded file!'); - }, - onError(err) { - toast.error(err.message); - } - }); useEffect(() => { if (path === '' && locations.data?.[0]) { @@ -101,7 +93,7 @@ function HostedLocationsPlayground() { } }, [path, locations.data]); - const isLoading = createLocation.isLoading || removeLocation.isLoading || doTheThing.isLoading; + const isLoading = createLocation.isLoading || removeLocation.isLoading; return ( <> @@ -153,19 +145,6 @@ function HostedLocationsPlayground() { > Delete - ))} diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 4c62391d6..6145a7dca 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -8,9 +8,13 @@ export type Procedures = { { key: "buildInfo", input: never, result: BuildInfo } | { key: "cloud.devices.get", input: DeviceGetRequest, result: Device } | { key: "cloud.devices.list", input: DeviceListRequest, result: Device[] } | + { key: "cloud.libraries.get", input: LibraryGetRequest, result: Library } | + { key: "cloud.libraries.list", input: LibraryListRequest, result: Library[] } | { key: "cloud.library.get", input: LibraryArgs, result: null } | { key: "cloud.library.list", input: never, result: null } | - { key: "cloud.locations.list", input: never, result: CloudLocation[] } | + { key: "cloud.locations.list", input: never, result: Core_CloudLocation[] } | + { key: "cloud.new_locations.get", input: LocationGetRequest, result: CloudLocation } | + { key: "cloud.new_locations.list", input: LocationListRequest, result: CloudLocation[] } | { key: "ephemeralFiles.getMediaData", input: string, result: MediaData | null } | { key: "files.get", input: LibraryArgs, result: ObjectWithFilePaths2 | null } | { key: "files.getConvertibleImageExtensions", input: never, result: string[] } | @@ -67,11 +71,17 @@ export type Procedures = { { key: "cloud.bootstrap", input: AccessToken, result: null } | { key: "cloud.devices.delete", input: DeviceDeleteRequest, result: null } | { key: "cloud.devices.update", input: DeviceUpdateRequest, result: null } | + { key: "cloud.libraries.create", input: LibraryArgs, result: null } | + { key: "cloud.libraries.join", input: string, result: null } | + { key: "cloud.libraries.update", input: LibraryUpdateRequest, result: null } | { key: "cloud.library.create", input: LibraryArgs, result: null } | { key: "cloud.library.join", input: string, result: null } | { key: "cloud.library.sync", input: LibraryArgs, result: null } | - { key: "cloud.locations.create", input: string, result: CloudLocation } | - { key: "cloud.locations.remove", input: string, result: CloudLocation } | + { key: "cloud.locations.create", input: string, result: Core_CloudLocation } | + { key: "cloud.locations.remove", input: string, result: Core_CloudLocation } | + { key: "cloud.new_locations.create", input: LocationCreateRequest, result: null } | + { key: "cloud.new_locations.delete", input: LocationDeleteRequest, result: null } | + { key: "cloud.new_locations.update", input: LocationUpdateRequest, result: null } | { key: "ephemeralFiles.copyFiles", input: LibraryArgs, result: null } | { key: "ephemeralFiles.createFile", input: LibraryArgs, result: string } | { key: "ephemeralFiles.createFolder", input: LibraryArgs, result: string } | @@ -183,7 +193,7 @@ export type ChangeNodeNameArgs = { name: string | null; p2p_port: Port | null; p export type Chapter = { id: number; start: [number, number]; end: [number, number]; time_base_den: number; time_base_num: number; metadata: Metadata } -export type CloudLocation = { id: string; name: string } +export type CloudLocation = { pub_id: LocationPubId; name: string; device: Device | null; library: Library | null; created_at: string; updated_at: string } export type Codec = { kind: string | null; sub_kind: string | null; tag: string | null; name: string | null; profile: string | null; bit_rate: number; props: Props | null } @@ -217,6 +227,8 @@ export type ConvertImageArgs = { location_id: number; file_path_id: number; dele export type ConvertibleExtension = "bmp" | "dib" | "ff" | "gif" | "ico" | "jpg" | "jpeg" | "png" | "pnm" | "qoi" | "tga" | "icb" | "vda" | "vst" | "tiff" | "tif" | "hif" | "heif" | "heifs" | "heic" | "heics" | "avif" | "avci" | "avcs" | "svg" | "svgz" | "pdf" | "webp" +export type Core_CloudLocation = { id: string; name: string } + export type CreateEphemeralFileArgs = { path: string; context: EphemeralFileCreateContextTypes; name: string | null } export type CreateEphemeralFolderArgs = { path: string; name: string | null } @@ -411,6 +423,8 @@ export type Label = { id: number; name: string; date_created: string | null; dat export type LabelWithObjects = { id: number; name: string; date_created: string | null; date_modified: string | null; label_objects: { object: { id: number; file_paths: FilePath[] } }[] } +export type Library = { pub_id: LibraryPubId; name: string; original_device: Device | null; created_at: string; updated_at: string } + /** * Can wrap a query argument to require it to contain a `library_id` and provide helpers for working with libraries. */ @@ -442,10 +456,18 @@ export type LibraryConfigVersion = "V0" | "V1" | "V2" | "V3" | "V4" | "V5" | "V6 export type LibraryConfigWrapped = { uuid: string; instance_id: string; instance_public_key: RemoteIdentity; config: LibraryConfig } +export type LibraryGetRequest = { access_token: AccessToken; pub_id: LibraryPubId; with_device: boolean } + +export type LibraryListRequest = { access_token: AccessToken; with_device: boolean } + export type LibraryName = string export type LibraryPreferences = { location?: { [key in string]: LocationSettings }; tag?: { [key in string]: TagSettings } } +export type LibraryPubId = string + +export type LibraryUpdateRequest = { access_token: AccessToken; pub_id: LibraryPubId; name: string } + export type LightScanArgs = { location_id: number; sub_path: string } export type ListenerState = { type: "Listening" } | { type: "Error"; error: string } | { type: "NotListening" } @@ -461,6 +483,16 @@ export type Location = { id: number; pub_id: number[]; name: string | null; path */ export type LocationCreateArgs = { path: string; dry_run: boolean; indexer_rules_ids: number[] } +export type LocationCreateRequest = { access_token: AccessToken; pub_id: LocationPubId; name: string; library_pub_id: LibraryPubId; device_pub_id: DevicePubId } + +export type LocationDeleteRequest = { access_token: AccessToken; pub_id: LocationPubId } + +export type LocationGetRequest = { access_token: AccessToken; pub_id: LocationPubId; with_library: boolean; with_device: boolean } + +export type LocationListRequest = { access_token: AccessToken; with_library: boolean; with_device: boolean } + +export type LocationPubId = string + export type LocationSettings = { explorer: ExplorerSettings } /** @@ -473,6 +505,8 @@ export type LocationSettings = { explorer: ExplorerSettings } */ export type LocationUpdateArgs = { id: number; name: string | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; indexer_rules_ids: number[]; path: string | null } +export type LocationUpdateRequest = { access_token: AccessToken; pub_id: LocationPubId; name: string } + export type LocationWithIndexerRule = { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; size_in_bytes: number[] | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; instance_id: number | null; indexer_rules: IndexerRule[] } export type MaybeUndefined = null | T