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.
This commit is contained in:
Arnab Chakraborty 2024-08-07 17:55:22 +03:00
parent 8718de0928
commit e2d1862766
10 changed files with 320 additions and 39 deletions

4
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

@ -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<Ctx> {
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 {

View File

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

View File

@ -517,6 +517,7 @@ pub mod library {
}
#[derive(Type, Serialize, Deserialize)]
#[specta(rename = "Core_CloudLocation")]
pub struct CloudLocation {
id: String,
name: String,

View File

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

View File

@ -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
</Button>
<Button
variant="accent"
size="sm"
onClick={() =>
doTheThing.mutate({
id: location.id,
path
})
}
disabled={isLoading}
>
Do the thing
</Button>
</div>
))}
</div>

View File

@ -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<null>, 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<number>, 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<null>, result: null } |
{ key: "cloud.libraries.join", input: string, result: null } |
{ key: "cloud.libraries.update", input: LibraryUpdateRequest, result: null } |
{ key: "cloud.library.create", input: LibraryArgs<null>, result: null } |
{ key: "cloud.library.join", input: string, result: null } |
{ key: "cloud.library.sync", input: LibraryArgs<null>, 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<EphemeralFileSystemOps>, result: null } |
{ key: "ephemeralFiles.createFile", input: LibraryArgs<CreateEphemeralFileArgs>, result: string } |
{ key: "ephemeralFiles.createFolder", input: LibraryArgs<CreateEphemeralFolderArgs>, 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<FilePathOrder> }
/**
@ -473,6 +505,8 @@ export type LocationSettings = { explorer: ExplorerSettings<FilePathOrder> }
*/
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<T> = null | T