Skip to content

Commit

Permalink
Revert "Revert "✨ Upload library/series/book thumbnails (#249)" (#251)"
Browse files Browse the repository at this point in the history
This reverts commit e6c1c9e.
  • Loading branch information
aaronleopold committed Jan 29, 2024
1 parent e6c1c9e commit fc254ea
Show file tree
Hide file tree
Showing 32 changed files with 942 additions and 156 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 8 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ version = "0.0.0"
rust-version = "1.72.1"

[workspace.dependencies]
async-trait = "0.1.74"
async-stream = "0.3.5"
bcrypt = "0.15.0"
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.6.10", features = [
"sqlite-create-many",
"migrations",
Expand All @@ -24,9 +27,12 @@ prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-
"sqlite",
"mocking"
], default-features = false }

reqwest = { version = "0.11.22", features = [ "json" ] }
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
specta = "1.0.5"
tempfile = "3.8.1"
thiserror = "1.0.51"
tokio = { version = "1.35.0", features = [
# Provides sender/reciever channels
"sync",
Expand All @@ -35,17 +41,5 @@ tokio = { version = "1.35.0", features = [
# Allows handling shutdown signals (e.g., ctrl+c)
"signal",
] }
async-trait = "0.1.74"
async-stream = "0.3.5"
urlencoding = "2.1.3"

### DEV UTILS ###
specta = "1.0.5"
tempfile = "3.8.1"

### AUTH ###
bcrypt = "0.15.0"

### Error Handling + Logging ###
tracing = "0.1.40"
thiserror = "1.0.51"
urlencoding = "2.1.3"
58 changes: 28 additions & 30 deletions apps/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,54 @@ edition = "2021"
default-run = "stump_server"

[dependencies]
stump_core = { path = "../../core" }
cli = { path = "../../crates/cli" }
prisma-client-rust = { workspace = true }
axum = { version = "0.6.20", features = ["ws", "headers"] }
async-stream = { workspace = true }
async-trait = { workspace = true }
axum = { version = "0.6.20", features = [
"ws",
"headers",
"multipart",
] }
axum-macros = "0.4.0"
axum-extra = { version = "0.5.0", features = [
"spa",
"query"
] }
base64 = "0.21.5"
bcrypt = { workspace = true }
cli = { path = "../../crates/cli" }
futures-util = "0.3.29"
hyper = "0.14.27"
local-ip-address = "0.5.6"
notify = "5.1.0"
prisma-client-rust = { workspace = true }
rand = "0.8.5"
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_qs = { version = "0.12.0", features = ["axum"] }
serde-untagged = "0.1.2"
specta = { workspace = true }
stump_core = { path = "../../core" }
tower-http = { version = "0.4.4", features = [
"fs",
"cors",
"set-header",
"trace"
] }
hyper = "0.14.27"
serde_json = { workspace = true }
futures-util = "0.3.29"
thiserror = { workspace = true }
time = "0.3.30"
tokio = { workspace = true }
tokio-util = "0.7.10"
serde = { workspace = true }
tower = "0.4.13"
tower-sessions = "0.2.1"
async-trait = { workspace = true }
async-stream = { workspace = true }
local-ip-address = "0.5.6"
notify = "5.1.0"
tracing = { workspace = true }
urlencoding = { workspace = true }
serde_qs = { version = "0.12.0", features = ["axum"] }
serde-untagged = "0.1.2"
time = "0.3.30"


### Dev Utils ###
rand = "0.8.5"
utoipa = { version = "3.5.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "3.1.5", features = ["axum"] }
specta = { workspace = true }

### Error Handling + Logging ###
tracing = { workspace = true }
thiserror = { workspace = true }

### Auth ###
bcrypt = { workspace = true }
base64 = "0.21.5"
[build-dependencies]
chrono = "0.4.31"

### Platform Specific Deps ###
[target.aarch64-unknown-linux-musl.dependencies]
openssl = { version = "0.10.61", features = ["vendored"] }

Expand All @@ -62,5 +62,3 @@ openssl = { version = "0.10.61", features = ["vendored"] }
[target.x86_64-unknown-linux-musl.dependencies]
openssl = { version = "0.10.61", features = ["vendored"] }

[build-dependencies]
chrono = "0.4.31"
16 changes: 16 additions & 0 deletions apps/server/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use axum::{
extract::multipart::MultipartError,
http::StatusCode,
response::{IntoResponse, Response},
};
Expand Down Expand Up @@ -83,6 +84,9 @@ impl IntoResponse for AuthError {
}
}

// TODO: ApiError -> APIError
// TODO: Serialization is really poor, need to fix that

#[allow(unused)]
#[derive(Debug, Error, ToSchema)]
pub enum ApiError {
Expand Down Expand Up @@ -115,6 +119,18 @@ pub enum ApiError {
PrismaError(#[from] QueryError),
}

impl From<MultipartError> for ApiError {
fn from(error: MultipartError) -> Self {
ApiError::InternalServerError(error.to_string())
}
}

impl From<reqwest::Error> for ApiError {
fn from(error: reqwest::Error) -> Self {
ApiError::InternalServerError(error.to_string())
}
}

impl ApiError {
pub fn forbidden_discreet() -> ApiError {
ApiError::Forbidden(String::from(
Expand Down
64 changes: 56 additions & 8 deletions apps/server/src/routers/api/v1/library.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use axum::{
extract::{Path, State},
extract::{DefaultBodyLimit, Multipart, Path, State},
middleware::from_extractor_with_state,
routing::{get, post, put},
Json, Router,
Expand Down Expand Up @@ -28,8 +28,9 @@ use stump_core::{
filesystem::{
get_unknown_thumnail,
image::{
self, generate_thumbnail, remove_thumbnails, remove_thumbnails_of_type,
ImageFormat, ImageProcessorOptions, ThumbnailJob, ThumbnailJobConfig,
self, generate_thumbnail, place_thumbnail, remove_thumbnails,
remove_thumbnails_of_type, ImageFormat, ImageProcessorOptions, ThumbnailJob,
ThumbnailJobConfig,
},
read_entire_file,
scanner::LibraryScanJob,
Expand All @@ -54,8 +55,8 @@ use crate::{
},
middleware::auth::Auth,
utils::{
get_session_server_owner_user, get_session_user, get_user_and_enforce_permission,
http::ImageResponse,
enforce_session_permissions, get_session_server_owner_user, get_session_user,
get_user_and_enforce_permission, http::ImageResponse, validate_image_upload,
},
};

Expand Down Expand Up @@ -98,6 +99,9 @@ pub(crate) fn mount(app_state: AppState) -> Router<AppState> {
"/",
get(get_library_thumbnail_handler)
.patch(patch_library_thumbnail)
.post(replace_library_thumbnail)
// TODO: configurable max file size
.layer(DefaultBodyLimit::max(20 * 1024 * 1024)) // 20MB
.delete(delete_library_thumbnails),
)
.route("/generate", post(generate_library_thumbnails)),
Expand Down Expand Up @@ -512,9 +516,9 @@ pub(crate) fn get_library_thumbnail(
tracing::trace!(?path, library_id, "Found generated library thumbnail");
return Ok((ContentType::from(format), read_entire_file(path)?));
}
} else if let Some(path) =
get_unknown_thumnail(&library_id, config.get_thumbnails_dir())
{
}

if let Some(path) = get_unknown_thumnail(&library_id, config.get_thumbnails_dir()) {
tracing::debug!(path = ?path, library_id, "Found library thumbnail that does not align with config");
let FileParts { extension, .. } = path.file_parts();
return Ok((
Expand Down Expand Up @@ -693,6 +697,50 @@ async fn patch_library_thumbnail(
)))
}

async fn replace_library_thumbnail(
Path(id): Path<String>,
State(ctx): State<AppState>,
session: Session,
mut upload: Multipart,
) -> ApiResult<ImageResponse> {
enforce_session_permissions(
&session,
&[UserPermission::UploadFile, UserPermission::ManageLibrary],
)?;
let client = ctx.get_db();

tracing::trace!(?id, ?upload, "Replacing library thumbnail");

let library = client
.library()
.find_unique(library::id::equals(id))
.exec()
.await?
.ok_or(ApiError::NotFound(String::from("Library not found")))?;

let (content_type, bytes) = validate_image_upload(&mut upload).await?;

let ext = content_type.extension();
let library_id = library.id;

// Note: I chose to *safely* attempt the removal as to not block the upload, however after some
// user testing I'd like to see if this becomes a problem. We'll see!
remove_thumbnails(&[library_id.clone()], ctx.config.get_thumbnails_dir())
.unwrap_or_else(|e| {
tracing::error!(
?e,
"Failed to remove existing library thumbnail before replacing!"
);
});

let path_buf = place_thumbnail(&library_id, ext, &bytes, &ctx.config)?;

Ok(ImageResponse::from((
content_type,
read_entire_file(path_buf)?,
)))
}

/// Deletes all media thumbnails in a library by id, if the current user has access to it.
#[utoipa::path(
delete,
Expand Down
Loading

0 comments on commit fc254ea

Please sign in to comment.