Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/database/content_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,54 @@ impl ContentFolderOperator {

Ok(model)
}

pub async fn ancestors(
&self,
folder: &Model,
) -> Result<ContentFolderAncestors, ContentFolderError> {
let mut ancestors = ContentFolderAncestors::default();

// Fetch the parent model
ancestors.parent = {
let Some(parent_id) = folder.parent_id else {
// No parent, no ancestors
return Ok(ancestors);
};

Some(self.find_by_id(parent_id).await?)
};

ancestors.breadcrumbs.push(PathBreadcrumb {
name: ancestors.parent.as_ref().unwrap().name.to_string(),
path: ancestors.parent.as_ref().unwrap().path.to_string(),
});

let mut next_id = ancestors.parent.as_ref().unwrap().parent_id;
while let Some(id) = next_id {
let folder = self.find_by_id(id).await?;
ancestors.breadcrumbs.push(PathBreadcrumb {
name: folder.name,
path: folder.path,
});
next_id = folder.parent_id;
}

// We walked from the bottom to the top of the folder hierarchy,
// but we want breadcrumbs navigation the other way around.
ancestors.breadcrumbs.reverse();

Ok(ancestors)
}
}

#[derive(Debug, Default)]
pub struct ContentFolderAncestors {
pub parent: Option<Model>,
pub breadcrumbs: Vec<PathBreadcrumb>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct PathBreadcrumb {
pub name: String,
pub path: String,
}
69 changes: 69 additions & 0 deletions src/extractors/folder_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use axum::extract::{FromRequestParts, Path};
use axum::http::request::Parts;
use snafu::prelude::*;

use crate::database::category::{self, CategoryOperator};
use crate::database::content_folder::{self, ContentFolderOperator, PathBreadcrumb};
use crate::state::AppState;
use crate::state::error::*;

#[derive(Clone, Debug)]
pub struct FolderRequest {
pub category: category::Model,
pub folder: content_folder::Model,
pub sub_folders: Vec<content_folder::Model>,
pub ancestors: Vec<PathBreadcrumb>,
pub parent: Option<content_folder::Model>,
}

impl FromRequestParts<AppState> for FolderRequest {
type Rejection = AppStateError;

async fn from_request_parts(
parts: &mut Parts,
app_state: &AppState,
) -> Result<Self, Self::Rejection> {
// This unwrap will only a category name is set, but no further folder path
// However, that case is handled by a different route (`routes::category::show`).
let Path((_category_name, folder_path)) = <Path<(String, String)> as FromRequestParts<
AppState,
>>::from_request_parts(parts, app_state)
.await
.unwrap();

// Read-only operators: no need to extract the current user
let category_operator = CategoryOperator::new(app_state.clone(), None);
let content_folder_operator = ContentFolderOperator::new(app_state.clone(), None);

// get current content folders with Path
let current_content_folder = content_folder_operator
// must format to add "/" in front of path like in DB
.find_by_path(format!("/{}", folder_path))
.await
.context(ContentFolderSnafu)?;

// Get all sub content folders of the current folder
let sub_content_folders: Vec<content_folder::Model> = content_folder_operator
.list_child_folders(current_content_folder.id)
.await
.context(ContentFolderSnafu)?;

let category: category::Model = category_operator
.find_by_id(current_content_folder.category_id)
.await
.context(CategorySnafu)?;

let ancestors = content_folder_operator
.ancestors(&current_content_folder)
.await
.context(ContentFolderSnafu)?;

Ok(Self {
category,
folder: current_content_folder,
sub_folders: sub_content_folders,
ancestors: ancestors.breadcrumbs,
parent: ancestors.parent,
})
}
}
1 change: 1 addition & 0 deletions src/extractors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod folder_request;
pub mod normalized_path;
pub mod torrent_list;
pub mod user;
2 changes: 1 addition & 1 deletion src/extractors/torrent_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl TorrentListFilter {
};

// Sort list by the latest added torrent (reverse order date_start)
list.sort_unstable_by(|a, b| b.date_start.cmp(&a.date_start));
list.sort_unstable_by_key(|b| std::cmp::Reverse(b.date_start));
list
}
}
Expand Down
75 changes: 9 additions & 66 deletions src/routes/content_folder.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use askama::Template;
use askama_web::WebTemplate;
use axum::Form;
use axum::extract::{Path, State};
use axum::extract::State;
use axum::response::{IntoResponse, Redirect};
use axum_extra::extract::CookieJar;
use camino::Utf8PathBuf;
use serde::{Deserialize, Serialize};
use snafu::prelude::*;

use crate::database::category::CategoryOperator;
use crate::database::content_folder::ContentFolderOperator;
use crate::database::content_folder::{ContentFolderOperator, PathBreadcrumb};
use crate::database::{category, content_folder};
use crate::extractors::folder_request::FolderRequest;
use crate::extractors::user::User;
use crate::state::flash_message::{OperationStatus, get_cookie};
use crate::state::{AppState, AppStateContext, error::*};
Expand Down Expand Up @@ -44,82 +45,24 @@ pub struct ContentFolderShowTemplate {
pub flash: Option<OperationStatus>,
}

pub struct PathBreadcrumb {
pub name: String,
pub path: String,
}

pub async fn show(
State(app_state): State<AppState>,
folder: FolderRequest,
user: Option<User>,
Path((_category_name, folder_path)): Path<(String, String)>,
jar: CookieJar,
) -> Result<(CookieJar, ContentFolderShowTemplate), AppStateError> {
let app_state_context = app_state.context().await?;

let content_folder_operator = ContentFolderOperator::new(app_state.clone(), user.clone());

// get current content folders with Path
let current_content_folder = content_folder_operator
// must format to add "/" in front of path like in DB
.find_by_path(format!("/{}", folder_path))
.await
.context(ContentFolderSnafu)?;

// Get all sub content folders of the current folder
let sub_content_folders: Vec<content_folder::Model> = content_folder_operator
.list_child_folders(current_content_folder.id)
.await
.context(ContentFolderSnafu)?;

// Get current categories
let category: category::Model = CategoryOperator::new(app_state.clone(), user.clone())
.find_by_id(current_content_folder.category_id)
.await
.context(CategorySnafu)?;

// create breadcrumb with ancestor of current folders
let mut content_folder_ancestors: Vec<PathBreadcrumb> = Vec::new();
// To get Current Parent Folder
let mut parent_folder: Option<content_folder::Model> = None;

content_folder_ancestors.push(PathBreadcrumb {
name: current_content_folder.name.clone(),
path: current_content_folder.path.clone(),
});

let mut current_id = current_content_folder.parent_id;
while let Some(id) = current_id {
let folder = content_folder_operator
.find_by_id(id)
.await
.context(ContentFolderSnafu)?;

if parent_folder.is_none() {
parent_folder = Some(folder.clone());
}

content_folder_ancestors.push(PathBreadcrumb {
name: folder.name,
path: folder.path,
});

current_id = folder.parent_id;
}

// Reverse the ancestor to create Breadrumb
content_folder_ancestors.reverse();

let (jar, operation_status) = get_cookie(jar);

Ok((
jar,
ContentFolderShowTemplate {
parent_folder,
breadcrumb_items: content_folder_ancestors,
sub_content_folders,
current_content_folder,
category,
parent_folder: folder.parent,
breadcrumb_items: folder.ancestors,
sub_content_folders: folder.sub_folders,
current_content_folder: folder.folder,
category: folder.category,
state: app_state_context,
user,
flash: operation_status,
Expand Down