[ENG-659] Spacedrive On Jamie's NAS (#866)

* serve dem

* 0% chance of working

* With a side of Vite plz

* no CI for now

* Clippy plz work

* undo

* bruh

* plz work
This commit is contained in:
Oscar Beaumont 2023-05-29 18:47:59 +08:00 committed by GitHub
parent 6c82f1f2c8
commit a20f6262fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 156 additions and 28 deletions

View file

@ -3,7 +3,11 @@ description: Builds the server and publishes its Docker image
runs: runs:
using: 'composite' using: 'composite'
steps: steps:
- name: Copy binary to Docker context - name: Build web
shell: bash
run: pnpm web build
- name: Build binary & move into Docker context
shell: bash shell: bash
run: | run: |
cargo build -p server --release cargo build -p server --release

1
.gitignore vendored
View file

@ -2,6 +2,7 @@ node_modules
.next .next
dist dist
!apps/desktop/dist !apps/desktop/dist
!apps/web/dist
*.tsbuildinfo *.tsbuildinfo
package-lock.json package-lock.json
.eslintcache .eslintcache

45
Cargo.lock generated
View file

@ -534,7 +534,7 @@ dependencies = [
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tower", "tower",
"tower-http", "tower-http 0.3.4",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
] ]
@ -3277,9 +3277,9 @@ dependencies = [
[[package]] [[package]]
name = "include_dir" name = "include_dir"
version = "0.7.2" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "482a2e29200b7eed25d7fdbd14423326760b7f6658d21a4cf12d55a50713c69f" checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e"
dependencies = [ dependencies = [
"glob", "glob",
"include_dir_macros", "include_dir_macros",
@ -4289,6 +4289,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "mini-moka" name = "mini-moka"
version = "0.10.0" version = "0.10.0"
@ -7286,10 +7296,12 @@ dependencies = [
"ctrlc", "ctrlc",
"http", "http",
"httpz 0.0.3", "httpz 0.0.3",
"hyper", "include_dir",
"mime_guess",
"rspc", "rspc",
"sd-core", "sd-core",
"tokio", "tokio",
"tower-http 0.4.0",
"tracing", "tracing",
] ]
@ -8538,6 +8550,31 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "tower-http"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"httpdate",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "tower-layer" name = "tower-layer"
version = "0.3.2" version = "0.3.2"

View file

@ -55,7 +55,7 @@ pub async fn get_file_path_open_with_apps(
return Err(()) return Err(())
}; };
let Ok(Some(path)) = library let Ok(Some(_path)) = library
.get_file_path(id) .get_file_path(id)
.await .await
else { else {
@ -64,7 +64,7 @@ pub async fn get_file_path_open_with_apps(
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
return Ok(unsafe { return Ok(unsafe {
sd_desktop_macos::get_open_with_applications(&path.to_str().unwrap().into()) sd_desktop_macos::get_open_with_applications(&_path.to_str().unwrap().into())
} }
.as_slice() .as_slice()
.iter() .iter()
@ -83,14 +83,14 @@ pub async fn get_file_path_open_with_apps(
pub async fn open_file_path_with( pub async fn open_file_path_with(
library: uuid::Uuid, library: uuid::Uuid,
id: i32, id: i32,
with_url: String, _with_url: String,
node: tauri::State<'_, Arc<Node>>, node: tauri::State<'_, Arc<Node>>,
) -> Result<(), ()> { ) -> Result<(), ()> {
let Some(library) = node.library_manager.get_library(library).await else { let Some(library) = node.library_manager.get_library(library).await else {
return Err(()) return Err(())
}; };
let Ok(Some(path)) = library let Ok(Some(_path)) = library
.get_file_path(id) .get_file_path(id)
.await .await
else { else {
@ -100,8 +100,8 @@ pub async fn open_file_path_with(
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
unsafe { unsafe {
sd_desktop_macos::open_file_path_with( sd_desktop_macos::open_file_path_with(
&path.to_str().unwrap().into(), &_path.to_str().unwrap().into(),
&with_url.as_str().into(), &_with_url.as_str().into(),
) )
}; };

View file

@ -5,6 +5,9 @@ license.workspace = true
repository.workspace = true repository.workspace = true
edition.workspace = true edition.workspace = true
[features]
assets = []
[dependencies] [dependencies]
sd-core = { path = "../../core", features = ["ffmpeg"] } sd-core = { path = "../../core", features = ["ffmpeg"] }
rspc = { workspace = true, features = ["axum"] } rspc = { workspace = true, features = ["axum"] }
@ -14,4 +17,6 @@ tokio = { workspace = true, features = ["sync", "rt-multi-thread", "signal"] }
tracing = "0.1.36" tracing = "0.1.36"
ctrlc = "3.2.3" ctrlc = "3.2.3"
http = "0.2.8" http = "0.2.8"
hyper = "0.14.23" tower-http = { version = "0.4.0", features = ["fs"] }
include_dir = "0.7.3"
mime_guess = "2.0.4"

View file

@ -6,6 +6,10 @@ use tracing::info;
mod utils; mod utils;
#[cfg(feature = "assets")]
static ASSETS_DIR: include_dir::Dir<'static> =
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../web/dist");
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let data_dir = match env::var("DATA_DIR") { let data_dir = match env::var("DATA_DIR") {
@ -32,13 +36,85 @@ async fn main() {
let signal = utils::axum_shutdown_signal(node.clone()); let signal = utils::axum_shutdown_signal(node.clone());
let app = axum::Router::new() let app = axum::Router::new()
.route("/", get(|| async { "Spacedrive Server!" }))
.route("/health", get(|| async { "OK" })) .route("/health", get(|| async { "OK" }))
.nest( .nest(
"/spacedrive", "/spacedrive",
create_custom_uri_endpoint(node.clone()).axum(), create_custom_uri_endpoint(node.clone()).axum(),
) )
.nest("/rspc", router.endpoint(move || node.clone()).axum()) .nest("/rspc", router.endpoint(move || node.clone()).axum());
#[cfg(feature = "assets")]
let app = app
.route(
"/",
get(|| async move {
use axum::{
body::{self, Full},
response::Response,
};
use http::{header, HeaderValue, StatusCode};
match ASSETS_DIR.get_file("index.html") {
Some(file) => Response::builder()
.status(StatusCode::OK)
.header(
header::CONTENT_TYPE,
HeaderValue::from_str("text/html").unwrap(),
)
.body(body::boxed(Full::from(file.contents())))
.unwrap(),
None => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(body::boxed(axum::body::Empty::new()))
.unwrap(),
}
}),
)
.route(
"/*id",
get(
|axum::extract::Path(path): axum::extract::Path<String>| async move {
use axum::{
body::{self, Empty, Full},
response::Response,
};
use http::{header, HeaderValue, StatusCode};
let path = path.trim_start_matches('/');
match ASSETS_DIR.get_file(path) {
Some(file) => Response::builder()
.status(StatusCode::OK)
.header(
header::CONTENT_TYPE,
HeaderValue::from_str(
mime_guess::from_path(path).first_or_text_plain().as_ref(),
)
.unwrap(),
)
.body(body::boxed(Full::from(file.contents())))
.unwrap(),
None => match ASSETS_DIR.get_file("index.html") {
Some(file) => Response::builder()
.status(StatusCode::OK)
.header(
header::CONTENT_TYPE,
HeaderValue::from_str("text/html").unwrap(),
)
.body(body::boxed(Full::from(file.contents())))
.unwrap(),
None => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(body::boxed(Empty::new()))
.unwrap(),
},
}
},
),
);
#[cfg(not(feature = "assets"))]
let app = app
.route("/", get(|| async { "Spacedrive Server!" }))
.fallback(|| async { "404 Not Found: We're past the event horizon..." }); .fallback(|| async { "404 Not Found: We're past the event horizon..." });
let mut addr = "[::]:8080".parse::<SocketAddr>().unwrap(); // This listens on IPv6 and IPv4 let mut addr = "[::]:8080".parse::<SocketAddr>().unwrap(); // This listens on IPv6 and IPv4

5
apps/web/dist/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
# This is done so that `include_dir` never complains that '../dist does not exist'

View file

@ -103,8 +103,6 @@ export type MasterPasswordChangeArgs = { password: Protected<string>; algorithm:
*/ */
export type NodeConfig = { id: string; name: string; p2p_port: number | null; p2p_email: string | null; p2p_img_url: string | null } export type NodeConfig = { id: string; name: string; p2p_port: number | null; p2p_email: string | null; p2p_img_url: string | null }
export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string }
/** /**
* This denotes the `StoredKey` version. * This denotes the `StoredKey` version.
*/ */
@ -123,8 +121,6 @@ export type GenerateThumbsForLocationArgs = { id: number; path: string }
export type LibraryConfigWrapped = { uuid: string; config: LibraryConfig } export type LibraryConfigWrapped = { uuid: string; config: LibraryConfig }
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
/** /**
* These parameters define the password-hashing level. * These parameters define the password-hashing level.
* *
@ -142,6 +138,8 @@ export type Params = "Standard" | "Hardened" | "Paranoid"
*/ */
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[] } 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[] }
export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string }
export type SortOrder = "Asc" | "Desc" export type SortOrder = "Asc" | "Desc"
/** /**
@ -210,14 +208,14 @@ export type SetFavoriteArgs = { id: number; favorite: boolean }
export type FilePathFilterArgs = { locationId?: number | null; search?: string; extension?: string | null; createdAt?: OptionalRange<string>; path?: string | null; object?: ObjectFilterArgs | null } export type FilePathFilterArgs = { locationId?: number | null; search?: string; extension?: string | null; createdAt?: OptionalRange<string>; path?: string | null; object?: ObjectFilterArgs | null }
export type Statistics = { id: number; date_captured: string; total_object_count: number; library_db_size: string; total_bytes_used: string; total_bytes_capacity: string; total_unique_bytes: string; total_bytes_free: string; preview_media_bytes: string }
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent" export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string } export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null }
export type FilePathSearchOrdering = { name: SortOrder } | { sizeInBytes: SortOrder } | { dateCreated: SortOrder } | { dateModified: SortOrder } | { dateIndexed: SortOrder } | { object: ObjectSearchOrdering } export type FilePathSearchOrdering = { name: SortOrder } | { sizeInBytes: SortOrder } | { dateCreated: SortOrder } | { dateModified: SortOrder } | { dateIndexed: SortOrder } | { object: ObjectSearchOrdering }
export type IndexerRule = { id: number; name: string; default: boolean; rules_per_kind: number[]; date_created: string; date_modified: string }
export type BuildInfo = { version: string; commit: string } export type BuildInfo = { version: string; commit: string }
export type IdentifyUniqueFilesArgs = { id: number; path: string } export type IdentifyUniqueFilesArgs = { id: number; path: string }
@ -227,9 +225,9 @@ export type IdentifyUniqueFilesArgs = { id: number; path: string }
*/ */
export type Algorithm = "XChaCha20Poly1305" | "Aes256Gcm" export type Algorithm = "XChaCha20Poly1305" | "Aes256Gcm"
export type OwnedOperationItem = { id: any; data: OwnedOperationData } export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string }
export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null } export type OwnedOperationItem = { id: any; data: OwnedOperationData }
export type ObjectSearchOrdering = { dateAccessed: SortOrder } export type ObjectSearchOrdering = { dateAccessed: SortOrder }
@ -300,7 +298,7 @@ export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMa
export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | null export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | null
export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string } export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
export type TagUpdateArgs = { id: number; name: string | null; color: string | null } export type TagUpdateArgs = { id: number; name: string | null; color: string | null }
@ -310,8 +308,6 @@ export type TagAssignArgs = { object_id: number; tag_id: number; unassign: boole
export type ChangeNodeNameArgs = { name: string } export type ChangeNodeNameArgs = { name: string }
export type Object = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null }
/** /**
* This defines all available password hashing algorithms. * This defines all available password hashing algorithms.
*/ */
@ -321,8 +317,6 @@ export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Faile
export type FilePathWithObject = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string; object: Object | null } export type FilePathWithObject = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string; object: Object | null }
export type IndexerRule = { id: number; name: string; default: boolean; rules_per_kind: number[]; date_created: string; date_modified: string }
export type LocationWithIndexerRules = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string; indexer_rules: { indexer_rule: IndexerRule }[] } export type LocationWithIndexerRules = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string; indexer_rules: { indexer_rule: IndexerRule }[] }
/** /**
@ -334,12 +328,18 @@ export type SearchData<T> = { cursor: number[] | null; items: T[] }
export type CreateLibraryArgs = { name: string } export type CreateLibraryArgs = { name: string }
export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string }
export type AutomountUpdateArgs = { uuid: string; status: boolean } export type AutomountUpdateArgs = { uuid: string; status: boolean }
export type Statistics = { id: number; date_captured: string; total_object_count: number; library_db_size: string; total_bytes_used: string; total_bytes_capacity: string; total_unique_bytes: string; total_bytes_free: string; preview_media_bytes: string }
export type Protected<T> = T export type Protected<T> = T
export type RestoreBackupArgs = { password: Protected<string>; secret_key: Protected<string>; path: string } export type RestoreBackupArgs = { password: Protected<string>; secret_key: Protected<string>; path: string }
export type Object = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null }
export type RelationOperation = { relation_item: string; relation_group: string; relation: string; data: RelationOperationData } export type RelationOperation = { relation_item: string; relation_group: string; relation: string; data: RelationOperationData }
/** /**