Skip to content

Commit

Permalink
Merge branch 'master' into register_if_no_admin
Browse files Browse the repository at this point in the history
  • Loading branch information
vgarleanu authored Nov 29, 2023
2 parents 51d968e + aa0743b commit 0260db9
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 51 deletions.
4 changes: 2 additions & 2 deletions dim-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
//! It uses Diesel as the ORM and rocket for the http/s server
//!
//! The project is split up into several crates:
//! * [`database`](database) - Holds all the database models including some frequently used db operations
//! * [`database`](dim-database) - Holds all the database models including some frequently used db operations
//! * [`routes`](routes) - All of the routes that we expose over http are stored in there
//! * [`scanners`](scanners) - The filesystem scanner and daemon code is located here
//! * [`scanners`](scanner) - The filesystem scanner and daemon code is located here
//! ffmpeg that is used by several parts of dim
//!
//! # Building
Expand Down
6 changes: 3 additions & 3 deletions dim-database/src/media.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ impl PartialEq for Media {

impl Media {
/// Method returns all Media objects associated with a Library. Its exactly the same as
/// [`Library::get`](Library::get) except it takes in a Library object instead of a id.
/// [`Library::get`](Library::get) is a intermediary to this function, as it calls this
/// [`Library::get`](dim-database::library::Library::get) except it takes in a Library object instead of a id.
/// [`Library::get`](dim-database::library::Library::get) is a intermediary to this function, as it calls this
/// function.
///
/// # Arguments
/// * `conn` - mutable reference to a sqlx transaction.
/// * `library_id` - a [`Library`](Library) id.
/// * `library_id` - a [`Library`](dim-database::library::Library) id.
pub async fn get_all(
conn: &mut crate::Transaction<'_>,
library_id: i64,
Expand Down
8 changes: 4 additions & 4 deletions dim-database/src/mediafile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use std::iter::repeat;
pub struct MediaFile {
/// Unique identifier of a mediafile.
pub id: i64,
/// Foreign key linking this entry to the media table or [`Media`](Media) struct
/// Foreign key linking this entry to the media table or [`Media`] struct
pub media_id: Option<i64>,
/// Library foreign key linking this entry to the library table or [`Library`](Library) struct
/// Library foreign key linking this entry to the library table or [`Library`](dim-database::library::Library) struct
pub library_id: i64,
/// String representing the file path of the file we target. This should be a real path on the
/// filesystem.
Expand Down Expand Up @@ -243,7 +243,7 @@ impl MediaFile {
}
}

/// Same as [`MediaFile`](MediaFile) except its missing the id field.
/// Same as [`MediaFile`] except its missing the id field.
#[derive(Clone, Serialize, Debug, Default)]
pub struct InsertableMediaFile {
pub media_id: Option<i64>,
Expand Down Expand Up @@ -322,7 +322,7 @@ impl InsertableMediaFile {
}
}

/// Same as [`MediaFile`](MediaFile) except its missing the id and library_id fields. Everything is
/// Same as [`MediaFile`] except its missing the id and library_id fields. Everything is
/// optional too.
#[derive(Clone, Default, Deserialize, PartialEq, Debug)]
pub struct UpdateMediaFile {
Expand Down
2 changes: 1 addition & 1 deletion dim-database/src/season.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl Season {
}

/// Struct representing a insertable season
/// Its exactly the same as [`Season`](Season) except it misses the tvshowid field and the id
/// Its exactly the same as [`Season`] except it misses the tvshowid field and the id
/// field.
#[derive(Clone, Serialize, Deserialize, Default)]
pub struct InsertableSeason {
Expand Down
8 changes: 4 additions & 4 deletions dim-extern-api/src/tmdb/metadata_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,15 +444,15 @@ impl TMDBMetadataProvider {
}
}

// -- TMDBMetadataProviderRef<T>
// -- MetadataProviderOf<T>

/// Used to key [TMDBMetadataProviderRef] to search for TV shows, compliments [Movies].
/// Used to key [`MetadataProviderOf`] to search for TV shows, compliments [Movies].
pub struct TvShows;

/// Used to key [TMDBMetadataProviderRef] to search for movies, compliments [TvShows].
/// Used to key [`MetadataProviderOf`] to search for movies, compliments [TvShows].
pub struct Movies;

/// An instance of [TMDBMetadataProvider] with a generic parameter to infer the [MediaType] for searches.
/// An instance of [`TMDBMetadataProvider`] with a generic parameter to infer the [`MediaType`](dim-database::library::MediaType) for searches.
pub struct MetadataProviderOf<K>
where
K: sealed::AssocMediaTypeConst + Send + Sync + 'static,
Expand Down
24 changes: 12 additions & 12 deletions dim-web/src/routes/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! # Request Authentication and Authorization
//! Most API endpoints require a valid JWT authentication token. If no such token is supplied, the
//! API will return [`Unauthenticated`]. Authentication tokens can be obtained by logging in with
//! API will return [`AuthError`]. Authentication tokens can be obtained by logging in with
//! the [`login`] method. Authentication tokens must be passed to the server through a
//! `Authroization` header.
//!
Expand All @@ -16,7 +16,7 @@
//! By default tokens expire after exactly two weeks, once the tokens expire the client must renew
//! them. At the moment renewing the token is only possible by logging in again.
//!
//! [`Unauthenticated`]: crate::errors::DimError::Unauthenticated
//! [`AuthError`]
//! [`login`]: fn@login
use crate::AppState;
Expand Down Expand Up @@ -103,9 +103,9 @@ impl IntoResponse for AuthError {
/// ```
///
/// # Errors
/// * [`Unauthorized`] - Returned if the authentication token lacks `owner` permissions
/// * [`AuthError`] - Returned if the authentication token lacks `owner` permissions
///
/// [`Unauthorized`]: crate::errors::DimError::Unauthorized
/// [`AuthError`]
pub async fn get_all_invites(
Extension(user): Extension<User>,
State(AppState { conn, .. }): State<AppState>,
Expand Down Expand Up @@ -181,9 +181,9 @@ pub async fn get_all_invites(
/// ```
///
/// # Errors
/// * [`Unauthorized`] - Returned if the authentication token lacks `owner` permissions
/// * [`AuthError`] - Returned if the authentication token lacks `owner` permissions
///
/// [`Unauthorized`]: crate::errors::DimError::Unauthorized
/// [`AuthError`]
pub async fn generate_invite(
Extension(user): Extension<User>,
State(AppState { conn, .. }): State<AppState>,
Expand Down Expand Up @@ -221,9 +221,9 @@ pub async fn generate_invite(
/// If the token was successfully deleted, this route will return `200 0K`.
///
/// # Errors
/// * [`Unauthorized`] - Returned if the authentication token lacks `owner` permissions
/// * [`AuthError`] - Returned if the authentication token lacks `owner` permissions
///
/// [`Unauthorized`]: crate::errors::DimError::Unauthorized
/// [`AuthError`]
pub async fn delete_token(
Extension(user): Extension<User>,
State(AppState { conn, .. }): State<AppState>,
Expand Down Expand Up @@ -338,9 +338,9 @@ impl IntoResponse for LoginError {
/// ```
///
/// # Errors
/// * [`InvalidCredentials`] - The provided username or password is incorrect.
/// * [`LoginError`] - The provided username or password is incorrect.
///
/// [`InvalidCredentials`]: crate::errors::DimError::InvalidCredentials
/// [`LoginError`]
/// [`Login`]: dim_database::user::Login
#[axum::debug_handler]
pub async fn login(
Expand Down Expand Up @@ -421,10 +421,10 @@ impl IntoResponse for RegisterError {
/// ```
///
/// # Errors
/// * [`NoToken`] - Either the request doesnt contain an invite token, or the invite token is
/// * [`RegisterError`] - Either the request doesnt contain an invite token, or the invite token is
/// invalid.
///
/// [`NoToken`]: crate::errors::DimError::NoToken
/// [`RegisterError`]
/// [`Login`]: dim_database::user::Login
#[axum::debug_handler]
pub async fn register(
Expand Down
25 changes: 22 additions & 3 deletions dim-web/src/routes/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::collections::HashMap;
use std::sync::Arc;

use axum::Extension;
use axum::extract::{Path, Query, State};
use axum::response::{IntoResponse, Response};
use axum::Json;
Expand All @@ -13,13 +14,15 @@ use dim_database::compact_mediafile::CompactMediafile;
use dim_database::library::{InsertableLibrary, Library, MediaType};
use dim_database::media::Media;
use dim_database::mediafile::MediaFile;
use dim_database::user::User;

use dim_extern_api::tmdb::TMDBMetadataProvider;

use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
use http::StatusCode;
use serde::Serialize;
use serde::Deserialize;

use crate::error::DimErrorWrapper;
use crate::AppState;
Expand All @@ -29,9 +32,16 @@ use crate::AppState;
/// been created. This method can only be accessed by authenticated users. Method returns 200 OK
///
pub async fn library_post(
Extension(user): Extension<User>,
State(state): State<AppState>,
Json(new_library): Json<InsertableLibrary>,
) -> Response {
if !user.has_role("owner") {
return (
StatusCode::UNAUTHORIZED,
"User account is not allowed to add a library.".to_string(),
).into_response();
}
let mut lock = state.conn.writer().lock_owned().await;

let mut tx = match dim_database::write_tx(&mut lock).await {
Expand Down Expand Up @@ -87,9 +97,13 @@ pub async fn library_post(

/// Method mapped to `DELETE /api/v1/library/<id>` deletes the library with the supplied id from the path.
pub async fn library_delete(
Extension(user): Extension<User>,
State(AppState { conn, .. }): State<AppState>,
Path(id): Path<i64>,
) -> Result<StatusCode, DimErrorWrapper> {
if !user.has_role("owner") {
return Err(DimErrorWrapper(DimError::Unauthorized));
}
// First we mark the library as scheduled for deletion which will make the library and all its
// content hidden. This is necessary because huge libraries take a long time to delete.
{
Expand Down Expand Up @@ -233,13 +247,18 @@ pub async fn library_get_media(State(AppState { conn, .. }): State<AppState>, Pa
Json(result).into_response()
}

/// Method mapped to `GET` /api/v1/library/<id>/unmatched` returns a list of all unmatched medias
#[derive(Deserialize)]
pub struct UnmatchedArgs {
search: Option<String>,
}

/// Method mapped to `GET /api/v1/library/<id>/unmatched` returns a list of all unmatched medias
/// to be displayed in the library pages.
///
pub async fn library_get_unmatched(
State(AppState { conn, .. }): State<AppState>,
Path(id): Path<i64>,
Query(search): Query<Option<String>>,
Query(params): Query<UnmatchedArgs>,
) -> Response {
let mut tx = match conn.read().begin().await {
Ok(tx) => tx,
Expand All @@ -264,7 +283,7 @@ pub async fn library_get_unmatched(
// we want to pre-sort to ensure our tree is somewhat ordered.
files.sort_by(|a, b| a.target_file.cmp(&b.target_file));

if let Some(search) = search {
if let Some(search) = params.search {
let matcher = SkimMatcherV2::default();

let mut matched_files = files
Expand Down
6 changes: 2 additions & 4 deletions dim-web/src/routes/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,7 @@ pub async fn get_init(
/// Method mapped to `/api/v1/stream/<id>/data/<chunk..>` returns a chunk for stream `id`.
pub async fn get_chunk(
State(AppState { state, .. }): State<AppState>,
Path(id): Path<String>,
Path(chunk): Path<PathBuf>,
Path((id, chunk)): Path<(String, PathBuf)>,
) -> Result<impl IntoResponse, errors::StreamingErrors> {
let extension = chunk
.extension()
Expand Down Expand Up @@ -643,8 +642,7 @@ pub async fn get_subtitle_ass(
/// on web platforms.
pub async fn should_client_hard_seek(
State(AppState { state, stream_tracking, .. }): State<AppState>,
Path(gid): Path<String>,
Path(chunk_num): Path<u32>,
Path((gid, chunk_num)): Path<(String, u32)>,
) -> Result<impl IntoResponse, errors::StreamingErrors> {
let gid = match Uuid::parse_str(gid.as_str()) {
Ok(x) => x,
Expand Down
2 changes: 1 addition & 1 deletion dim-web/src/routes/tv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub async fn get_tv_seasons(
Ok(axum::response::Json(json!(&Season::get_all(&mut tx, id).await?)).into_response())
}

/// Method mapped to `GET /api/v1/season/<id>` returns info about the season by <id>
/// Method mapped to `GET /api/v1/season/<id>` returns info about the season by `id`
///
/// # Arguments
/// * `id` - id of the season we want info about
Expand Down
9 changes: 6 additions & 3 deletions ui/src/Components/Sidebar/Libraries.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Library from "./Library";
function Libraries() {
const dispatch = useDispatch();

const user = useSelector((store) => store.user);
const libraries = useSelector((store) => store.library.fetch_libraries);
const ws = useWebSocket();

Expand Down Expand Up @@ -63,9 +64,11 @@ function Libraries() {
<section className="libraries">
<header>
<h4>Libraries</h4>
<NewLibraryModal>
<button className="openNewLibrary">+</button>
</NewLibraryModal>
{user.info.roles?.includes("owner") && (
<NewLibraryModal>
<button className="openNewLibrary">+</button>
</NewLibraryModal>
)}
</header>
<div className="list">{libs}</div>
</section>
Expand Down
19 changes: 12 additions & 7 deletions ui/src/Pages/Dashboard/Banners/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ interface Props {
}

function Banner({ data, isError, isFetching }: Props) {
const { libraries } = useAppSelector((store) => ({
const { libraries, user } = useAppSelector((store) => ({
libraries: store.library.fetch_libraries,
user: store.user,
}));

if (isFetching || isError) {
Expand All @@ -39,9 +40,11 @@ function Banner({ data, isError, isFetching }: Props) {
Populate the folders they are pointing to with media or add
another library with existing media
</p>
<NewLibraryModal>
<button>Add another library</button>
</NewLibraryModal>
{user.info.roles?.includes("owner") && (
<NewLibraryModal>
<button>Add another library</button>
</NewLibraryModal>
)}
</div>
</div>
);
Expand All @@ -56,9 +59,11 @@ function Banner({ data, isError, isFetching }: Props) {
You will be able to see all the media from your libraries here,
organized for quick and easy access.
</p>
<NewLibraryModal>
<button>Add library</button>
</NewLibraryModal>
{user.info.roles?.includes("owner") && (
<NewLibraryModal>
<button>Add library</button>
</NewLibraryModal>
)}
</div>
</div>
);
Expand Down
19 changes: 12 additions & 7 deletions ui/src/Pages/Library/Dropdown.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";
import { useParams } from "react-router";

import Delete from "./Actions/Delete";
Expand All @@ -11,6 +12,8 @@ function Dropdown() {
const dropdownRef = useRef(null);
const params = useParams();

const user = useSelector((store) => store.user);

const [dropdownVisible, setDropdownVisible] = useState(false);

const handleClick = useCallback((e) => {
Expand Down Expand Up @@ -43,13 +46,15 @@ function Dropdown() {
<div />
<div />
</div>
<div className={`dropDownContent visible-${dropdownVisible}`}>
<Delete id={params.id} />
<button className="rename">
Rename library
<EditIcon />
</button>
</div>
{user.info.roles?.includes("owner") && (
<div className={`dropDownContent visible-${dropdownVisible}`}>
<Delete id={params.id} />
<button className="rename">
Rename library
<EditIcon />
</button>
</div>
)}
</div>
);
}
Expand Down

0 comments on commit 0260db9

Please sign in to comment.