From 2d33ea122e16b61b76f659cf1bd6cd79da28fa58 Mon Sep 17 00:00:00 2001 From: maxDorninger <97409287+maxDorninger@users.noreply.github.com> Date: Thu, 26 Jun 2025 18:22:05 +0200 Subject: [PATCH] add movies carousel to dashboard and fixing movies routes in the backend, making the components more generic and reusable --- media_manager/main.py | 2 +- media_manager/movies/router.py | 28 ++++----- ...show-card.svelte => add-media-card.svelte} | 12 ++-- .../recommended-media-carousel.svelte | 16 +++++ .../recommended-shows-carousel.svelte | 36 ------------ web/src/routes/dashboard/+page.svelte | 40 ++++++++++--- .../routes/dashboard/tv/add-show/+page.svelte | 58 ++++++++++--------- 7 files changed, 101 insertions(+), 91 deletions(-) rename web/src/lib/components/{add-show-card.svelte => add-media-card.svelte} (86%) create mode 100644 web/src/lib/components/recommended-media-carousel.svelte delete mode 100644 web/src/lib/components/recommended-shows-carousel.svelte diff --git a/media_manager/main.py b/media_manager/main.py index f34c5a1..459bf42 100644 --- a/media_manager/main.py +++ b/media_manager/main.py @@ -217,7 +217,7 @@ if openid_client is not None: app.include_router(tv_router.router, prefix="/tv", tags=["tv"]) app.include_router(torrent_router.router, prefix="/torrent", tags=["torrent"]) -app.include_router(movies_router.router, prefix="/movie", tags=["movie"]) +app.include_router(movies_router.router, prefix="/movies", tags=["movie"]) app.mount( "/static/image", StaticFiles(directory=basic_config.image_directory), diff --git a/media_manager/movies/router.py b/media_manager/movies/router.py index cb6e91e..57ef3c5 100644 --- a/media_manager/movies/router.py +++ b/media_manager/movies/router.py @@ -36,7 +36,7 @@ router = APIRouter() @router.post( - "/movies", + "/", status_code=status.HTTP_201_CREATED, dependencies=[Depends(current_active_user)], responses={ @@ -70,7 +70,7 @@ def add_a_movie( @router.get( - "/movies", + "/", dependencies=[Depends(current_active_user)], response_model=list[PublicMovie], ) @@ -79,7 +79,7 @@ def get_all_movies(movie_service: movie_service_dep): @router.get( - "/movies/search", + "/search", dependencies=[Depends(current_active_user)], response_model=list[MetaDataProviderSearchResult], ) @@ -94,7 +94,7 @@ def search_for_movie( @router.get( - "/movies/popular", + "/recommended", dependencies=[Depends(current_active_user)], response_model=list[MetaDataProviderSearchResult], ) @@ -106,7 +106,7 @@ def get_popular_movies( @router.get( - "/movies/torrents", + "/torrents", dependencies=[Depends(current_active_user)], response_model=list[RichMovieTorrent], ) @@ -120,7 +120,7 @@ def get_all_movies_with_torrents(movie_service: movie_service_dep): @router.post( - "/movies/requests", + "/requests", status_code=status.HTTP_201_CREATED, response_model=MovieRequest, ) @@ -143,7 +143,7 @@ def create_movie_request( @router.get( - "/movies/requests", + "/requests", dependencies=[Depends(current_active_user)], response_model=list[RichMovieRequest], ) @@ -152,7 +152,7 @@ def get_all_movie_requests(movie_service: movie_service_dep): @router.put( - "/movies/requests/{movie_request_id}", + "/requests/{movie_request_id}", response_model=MovieRequest, ) def update_movie_request( @@ -172,7 +172,7 @@ def update_movie_request( @router.patch( - "/movies/requests/{movie_request_id}", status_code=status.HTTP_204_NO_CONTENT + "/requests/{movie_request_id}", status_code=status.HTTP_204_NO_CONTENT ) def authorize_request( movie_service: movie_service_dep, @@ -195,7 +195,7 @@ def authorize_request( @router.delete( - "/movies/requests/{movie_request_id}", + "/requests/{movie_request_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(current_superuser)], ) @@ -211,7 +211,7 @@ def delete_movie_request( @router.get( - "/movies/{movie_id}", + "/{movie_id}", dependencies=[Depends(current_active_user)], response_model=PublicMovie, ) @@ -220,7 +220,7 @@ def get_movie_by_id(movie_service: movie_service_dep, movie_id: MovieId): @router.get( - "/movies/{movie_id}/torrents", + "/{movie_id}/torrents", dependencies=[Depends(current_active_user)], response_model=list[PublicIndexerQueryResult], ) @@ -231,7 +231,7 @@ def get_all_available_torrents_for_a_movie( @router.post( - "/movies/{movie_id}/torrents", + "/{movie_id}/torrents", status_code=status.HTTP_201_CREATED, dependencies=[Depends(current_active_user)], response_model=Torrent, @@ -247,7 +247,7 @@ def download_torrent_for_movie( @router.get( - "/movies/{movie_id}/files", + "/{movie_id}/files", dependencies=[Depends(current_active_user)], response_model=list[PublicMovieFile], ) diff --git a/web/src/lib/components/add-show-card.svelte b/web/src/lib/components/add-media-card.svelte similarity index 86% rename from web/src/lib/components/add-show-card.svelte rename to web/src/lib/components/add-media-card.svelte index 352ea50..a86cf74 100644 --- a/web/src/lib/components/add-show-card.svelte +++ b/web/src/lib/components/add-media-card.svelte @@ -10,12 +10,12 @@ const apiUrl = env.PUBLIC_API_URL; let loading = $state(false); let errorMessage = $state(null); - let {result}: { result: MetaDataProviderShowSearchResult } = $props(); + let {result, isShow = true}: { result: MetaDataProviderShowSearchResult, isShow: boolean } = $props(); console.log('Add Show Card Result: ', result); - async function addShow() { + async function addMedia() { loading = true; - let url = new URL(apiUrl + '/tv/shows'); + let url = isShow ? new URL(apiUrl + '/tv/shows') : new URL(apiUrl + '/movies'); url.searchParams.append('show_id', String(result.external_id)); url.searchParams.append('metadata_provider', result.metadata_provider); const response = await fetch(url, { @@ -25,7 +25,7 @@ let responseData = await response.json(); console.log('Added Show: Response Data: ', responseData); if (response.ok) { - await goto(base + '/dashboard/tv/' + responseData.id); + await goto(`${base}/dashboard/${isShow ? 'tv' : 'movies'}/` + responseData.id); } else { errorMessage = 'Error occurred: ' + responseData; } @@ -62,12 +62,12 @@
diff --git a/web/src/lib/components/recommended-media-carousel.svelte b/web/src/lib/components/recommended-media-carousel.svelte new file mode 100644 index 0000000..19fcf75 --- /dev/null +++ b/web/src/lib/components/recommended-media-carousel.svelte @@ -0,0 +1,16 @@ + + +
+ {#each media.slice(0, 3) as mediaItem} + + {/each} +
+ diff --git a/web/src/lib/components/recommended-shows-carousel.svelte b/web/src/lib/components/recommended-shows-carousel.svelte deleted file mode 100644 index 729d78c..0000000 --- a/web/src/lib/components/recommended-shows-carousel.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - {#each shows as show} - - - - {/each} - - - - diff --git a/web/src/routes/dashboard/+page.svelte b/web/src/routes/dashboard/+page.svelte index 933ac90..5705805 100644 --- a/web/src/routes/dashboard/+page.svelte +++ b/web/src/routes/dashboard/+page.svelte @@ -2,7 +2,7 @@ import {Separator} from '$lib/components/ui/separator/index.js'; import * as Sidebar from '$lib/components/ui/sidebar/index.js'; import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js'; - import RecommendedShowsCarousel from '$lib/components/recommended-shows-carousel.svelte'; + import RecommendedMediaCarousel from '$lib/components/recommended-media-carousel.svelte'; import LoadingBar from '$lib/components/loading-bar.svelte'; import {base} from '$app/paths'; import {page} from '$app/state'; @@ -11,11 +11,15 @@ import {env} from "$env/dynamic/public"; const apiUrl = env.PUBLIC_API_URL; + let recommendedShows: any[] = []; - let loading = true; + let showsLoading = true; + + let recommendedMovies: any[] = []; + let moviesLoading = true; onMount(async () => { - const res = await fetch(apiUrl + '/tv/recommended', { + const showsRes = await fetch(apiUrl + '/tv/recommended', { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', @@ -23,8 +27,22 @@ credentials: 'include', method: 'GET' }); - recommendedShows = await res.json(); - loading = false; + recommendedShows = await showsRes.json(); + showsLoading = false + + const moviesRes = await fetch(apiUrl + '/movies/recommended', { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: 'include', + method: 'GET' + }); + recommendedMovies = await moviesRes.json(); + moviesLoading = false; + + + }); @@ -57,11 +75,19 @@

Trending Shows

- {#if loading} + {#if showsLoading} {:else} + + {/if} - +

+ Trending Movies +

+ {#if showsLoading} + + {:else} + {/if}
diff --git a/web/src/routes/dashboard/tv/add-show/+page.svelte b/web/src/routes/dashboard/tv/add-show/+page.svelte index a370302..91d7e66 100644 --- a/web/src/routes/dashboard/tv/add-show/+page.svelte +++ b/web/src/routes/dashboard/tv/add-show/+page.svelte @@ -10,45 +10,49 @@ import * as Collapsible from '$lib/components/ui/collapsible/index.js'; import type {MetaDataProviderShowSearchResult} from '$lib/types.js'; import * as RadioGroup from '$lib/components/ui/radio-group/index.js'; - import AddShowCard from '$lib/components/add-show-card.svelte'; + import AddMediaCard from '$lib/components/add-media-card.svelte'; import {toast} from 'svelte-sonner'; + import {onMount} from "svelte"; const apiUrl = env.PUBLIC_API_URL; let searchTerm: string = $state(''); let metadataProvider: string = $state('tmdb'); let results: MetaDataProviderShowSearchResult[] | null = $state(null); - + onMount(search) async function search() { + let url = new URL(apiUrl + '/tv/recommended'); if (searchTerm.length > 0) { let url = new URL(apiUrl + '/tv/search'); url.searchParams.append('query', searchTerm); url.searchParams.append('metadata_provider', metadataProvider); toast.info(`Searching for "${searchTerm}" using ${metadataProvider.toUpperCase()}...`); - try { - const response = await fetch(url, { - method: 'GET', - credentials: 'include' - }); - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Search failed: ${response.status} ${errorText || response.statusText}`); - } - results = await response.json(); - if (results && results.length > 0) { - toast.success(`Found ${results.length} result(s) for "${searchTerm}".`); - } else { - toast.info(`No results found for "${searchTerm}".`); - } - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : 'An unknown error occurred during search.'; - console.error('Search error:', error); - toast.error(errorMessage); - results = null; // Clear previous results on error + + } + + try { + const response = await fetch(url, { + method: 'GET', + credentials: 'include' + }); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Search failed: ${response.status} ${errorText || response.statusText}`); } - } else { - toast.warning('Please enter a search term.'); - results = null; + results = await response.json(); + if (searchTerm.length === 0) { + return + } + if (results && results.length > 0) { + toast.success(`Found ${results.length} result(s) for "${searchTerm}".`); + } else { + toast.info(`No results found for "${searchTerm}".`); + } + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'An unknown error occurred during search.'; + console.error('Search error:', error); + toast.error(errorMessage); + results = null; // Clear previous results on error } } @@ -131,7 +135,7 @@ md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5" > {#each results as result} - + {/each} {/if}