[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:
using: 'composite'
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
run: |
cargo build -p server --release

1
.gitignore vendored
View file

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

45
Cargo.lock generated
View file

@ -534,7 +534,7 @@ dependencies = [
"sync_wrapper",
"tokio",
"tower",
"tower-http",
"tower-http 0.3.4",
"tower-layer",
"tower-service",
]
@ -3277,9 +3277,9 @@ dependencies = [
[[package]]
name = "include_dir"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "482a2e29200b7eed25d7fdbd14423326760b7f6658d21a4cf12d55a50713c69f"
checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e"
dependencies = [
"glob",
"include_dir_macros",
@ -4289,6 +4289,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "mini-moka"
version = "0.10.0"
@ -7286,10 +7296,12 @@ dependencies = [
"ctrlc",
"http",
"httpz 0.0.3",
"hyper",
"include_dir",
"mime_guess",
"rspc",
"sd-core",
"tokio",
"tower-http 0.4.0",
"tracing",
]
@ -8538,6 +8550,31 @@ dependencies = [
"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]]
name = "tower-layer"
version = "0.3.2"

View file

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

View file

@ -5,6 +5,9 @@ license.workspace = true
repository.workspace = true
edition.workspace = true
[features]
assets = []
[dependencies]
sd-core = { path = "../../core", features = ["ffmpeg"] }
rspc = { workspace = true, features = ["axum"] }
@ -14,4 +17,6 @@ tokio = { workspace = true, features = ["sync", "rt-multi-thread", "signal"] }
tracing = "0.1.36"
ctrlc = "3.2.3"
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;
#[cfg(feature = "assets")]
static ASSETS_DIR: include_dir::Dir<'static> =
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../web/dist");
#[tokio::main]
async fn main() {
let data_dir = match env::var("DATA_DIR") {
@ -32,13 +36,85 @@ async fn main() {
let signal = utils::axum_shutdown_signal(node.clone());
let app = axum::Router::new()
.route("/", get(|| async { "Spacedrive Server!" }))
.route("/health", get(|| async { "OK" }))
.nest(
"/spacedrive",
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..." });
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 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.
*/
@ -123,8 +121,6 @@ export type GenerateThumbsForLocationArgs = { id: number; path: string }
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.
*
@ -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 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"
/**
@ -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 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 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 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 IdentifyUniqueFilesArgs = { id: number; path: string }
@ -227,9 +225,9 @@ export type IdentifyUniqueFilesArgs = { id: number; path: string }
*/
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 }
@ -300,7 +298,7 @@ export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMa
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 }
@ -310,8 +308,6 @@ export type TagAssignArgs = { object_id: number; tag_id: number; unassign: boole
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.
*/
@ -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 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 }[] }
/**
@ -334,12 +328,18 @@ export type SearchData<T> = { cursor: number[] | null; items: T[] }
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 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 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 }
/**