diff --git a/backend/src/tv/router.py b/backend/src/tv/router.py index fdf96f5..01b44e2 100644 --- a/backend/src/tv/router.py +++ b/backend/src/tv/router.py @@ -103,11 +103,17 @@ def get_season_requests(db: DbSessionDependency) -> list[RichSeasonRequest]: return tv.service.get_all_season_requests(db=db) -@router.delete("/seasons/requests/{request_id}", status_code=status.HTTP_204_NO_CONTENT, - dependencies=[Depends(current_active_user)]) -def delete_season_request(db: DbSessionDependency, request_id: SeasonRequestId): - tv.service.delete_season_request(db=db, season_request_id=request_id) - return +@router.delete("/seasons/requests/{request_id}", status_code=status.HTTP_204_NO_CONTENT, ) +def delete_season_request(db: DbSessionDependency, user: Annotated[User, Depends(current_active_user)], + request_id: SeasonRequestId): + request = tv.service.get_season_request_by_id(db=db, season_request_id=request_id) + if user.is_superuser or request.requested_by.id == user.id: + tv.service.delete_season_request(db=db, season_request_id=request_id) + log.info(f"User {user.id} deleted season request {request_id}.") + else: + log.warning(f"User {user.id} tried to delete season request {request_id} but is not authorized.") + return JSONResponse(status_code=status.HTTP_403_FORBIDDEN, + content={"message": "Not authorized to delete this request."}) diff --git a/backend/src/tv/service.py b/backend/src/tv/service.py index 1f2f4bf..e5f7111 100644 --- a/backend/src/tv/service.py +++ b/backend/src/tv/service.py @@ -31,6 +31,9 @@ def add_season_request(db: Session, season_request: SeasonRequest) -> None: tv.repository.add_season_request(db=db, season_request=season_request) +def get_season_request_by_id(db: Session, season_request_id: SeasonRequestId) -> SeasonRequest | None: + return tv.repository.get_season_request(db=db, season_request_id=season_request_id) + def update_season_request(db: Session, season_request: SeasonRequest) -> None: tv.repository.update_season_request(db=db, season_request=season_request) diff --git a/web/.prettierrc b/web/.prettierrc index b19073e..c5382df 100644 --- a/web/.prettierrc +++ b/web/.prettierrc @@ -3,10 +3,10 @@ "singleQuote": true, "trailingComma": "none", "printWidth": 100, - "plugins": [ - "prettier-plugin-svelte", - "prettier-plugin-tailwindcss" - ], + "plugins": [ + "prettier-plugin-svelte", + "prettier-plugin-tailwindcss" + ], "overrides": [ { "files": "*.svelte", diff --git a/web/package.json b/web/package.json index 19b78ff..b3c84df 100644 --- a/web/package.json +++ b/web/package.json @@ -32,13 +32,13 @@ "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^2.46.1", "globals": "^15.14.0", - "mode-watcher": "^1.0.7", + "mode-watcher": "^1.0.7", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.10", "svelte": "^5.0.0", "svelte-check": "^4.0.0", - "svelte-sonner": "^0.3.28", + "svelte-sonner": "^0.3.28", "tailwind-merge": "^3.2.0", "tailwind-variants": "^1.0.0", "tailwindcss": "^3.4.17", diff --git a/web/src/app.html b/web/src/app.html index da99290..0a61570 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -7,8 +7,6 @@ %sveltekit.head% -
- %sveltekit.body% -
+
%sveltekit.body%
diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts index cc86da6..6d5b65e 100644 --- a/web/src/hooks.server.ts +++ b/web/src/hooks.server.ts @@ -1,7 +1,6 @@ import {createImageOptimizer} from 'sveltekit-image-optimize'; import type {Handle} from '@sveltejs/kit'; import {createFileSystemCache} from 'sveltekit-image-optimize/cache-adapters'; -import type {HandleServerError} from '@sveltejs/kit'; const cache = createFileSystemCache('./cache'); const imageHandler = createImageOptimizer({ diff --git a/web/src/lib/components/add-show-card.svelte b/web/src/lib/components/add-show-card.svelte index 8a4f34c..ef06539 100644 --- a/web/src/lib/components/add-show-card.svelte +++ b/web/src/lib/components/add-show-card.svelte @@ -1,20 +1,13 @@ + @@ -69,4 +63,4 @@

{errorMessage}

{/if} -
\ No newline at end of file + diff --git a/web/src/lib/components/app-sidebar.svelte b/web/src/lib/components/app-sidebar.svelte index 789f4b5..e52a21c 100644 --- a/web/src/lib/components/app-sidebar.svelte +++ b/web/src/lib/components/app-sidebar.svelte @@ -3,7 +3,6 @@ import Send from '@lucide/svelte/icons/send'; import TvIcon from '@lucide/svelte/icons/tv'; import LayoutPanelLeft from '@lucide/svelte/icons/layout-panel-left'; - import DownloadIcon from '@lucide/svelte/icons/download'; const data = { navMain: [ @@ -18,16 +17,15 @@ url: '/dashboard/tv/add-show' }, { - title: 'Torrents', - url: '/dashboard/tv/torrents' - }, - { + title: 'Torrents', + url: '/dashboard/tv/torrents' + }, + { title: 'Requests', url: '/dashboard/tv/requests' } - - ] - }, + ] + } ], navSecondary: [ { @@ -57,10 +55,9 @@ import NavSecondary from '$lib/components/nav-secondary.svelte'; import NavUser from '$lib/components/nav-user.svelte'; import * as Sidebar from '$lib/components/ui/sidebar/index.js'; - import Command from '@lucide/svelte/icons/command'; import type {ComponentProps} from 'svelte'; import logo from '$lib/images/logo.svg'; - import {base} from "$app/paths"; + import {base} from '$app/paths'; let {ref = $bindable(null), ...restProps}: ComponentProps = $props(); diff --git a/web/src/lib/components/checkmark-x.svelte b/web/src/lib/components/checkmark-x.svelte index 4d2f0a1..be77f9f 100644 --- a/web/src/lib/components/checkmark-x.svelte +++ b/web/src/lib/components/checkmark-x.svelte @@ -4,8 +4,9 @@ let {state} = $props(); + {#if state} {:else} -{/if} \ No newline at end of file +{/if} diff --git a/web/src/lib/components/download-season-dialog.svelte b/web/src/lib/components/download-season-dialog.svelte index 8365312..f0694f6 100644 --- a/web/src/lib/components/download-season-dialog.svelte +++ b/web/src/lib/components/download-season-dialog.svelte @@ -1,318 +1,309 @@ {#snippet saveDirectoryPreview(show, filePathSuffix)} - /{getFullyQualifiedShowName(show)} [{show.metadata_provider}id-{show.external_id}]/ - Season XX/{show.name} SXXEXX {filePathSuffix === '' - ? '' - : ' - ' + filePathSuffix}.mkv + /{getFullyQualifiedShowName(show)} [{show.metadata_provider}id-{show.external_id}]/ Season XX/{show.name} + SXXEXX {filePathSuffix === '' ? '' : ' - ' + filePathSuffix}.mkv {/snippet} - Download Seasons - - - - Download a Season - - Search and download torrents for a specific season or season packs. - - - - - Standard Mode - Advanced Mode - - -
- {#if show?.seasons?.length > 0} - - -

- Enter the season's number you want to search for. The first, usually 1, or the - last season number usually yield the most season packs. Note that only Seasons - which are listed in the "Seasons" cell will be imported! -

- - - {filePathSuffix} - - None - 2160p - 1080p - 720p - 480p - 360p - - -

- This is necessary to differentiate between versions of the same season/show, for - example a 1080p and a 4K version of a season. -

- -

- {@render saveDirectoryPreview(show, filePathSuffix)} -

- {:else} -

- No season information available for this show. -

- {/if} -
-
- -
- {#if show?.seasons?.length > 0} - -
- - -
-

- The custom query will override the default search string like "The Simpsons - Season 3". Note that only Seasons which are listed in the "Seasons" cell will be - imported! -

- - -

- This is necessary to differentiate between versions of the same season/show, for - example a 1080p and a 4K version of a season. -

+ Download Seasons + + + Download a Season + + Search and download torrents for a specific season or season packs. + + + + + Standard Mode + Advanced Mode + + +
+ {#if show?.seasons?.length > 0} + + +

+ Enter the season's number you want to search for. The first, usually 1, or the last + season number usually yield the most season packs. Note that only Seasons which are + listed in the "Seasons" cell will be imported! +

+ + + {filePathSuffix} + + None + 2160p + 1080p + 720p + 480p + 360p + + +

+ This is necessary to differentiate between versions of the same season/show, for + example a 1080p and a 4K version of a season. +

+ +

+ {@render saveDirectoryPreview(show, filePathSuffix)} +

+ {:else} +

+ No season information available for this show. +

+ {/if} +
+
+ +
+ {#if show?.seasons?.length > 0} + +
+ + +
+

+ The custom query will override the default search string like "The Simpsons Season 3". + Note that only Seasons which are listed in the "Seasons" cell will be imported! +

+ + +

+ This is necessary to differentiate between versions of the same season/show, for + example a 1080p and a 4K version of a season. +

- -

- {@render saveDirectoryPreview(show, filePathSuffix)} -

- {:else} -

- No season information available for this show. -

- {/if} -
-
-
-
- {#if isLoadingTorrents} -
- -

Loading torrents...

-
- {:else if torrentsError} -

Error: {torrentsError}

- {:else if torrents.length > 0} -

Found Torrents:

-
- - - - Title - Size - Seeders - Indexer Flags - Seasons - Actions - - - - {#each torrents as torrent (torrent.id)} - - {torrent.title} - {(torrent.size / 1024 / 1024 / 1024).toFixed(2)}GB - - {torrent.seeders} - - {#each torrent.flags as flag} - {flag},  - {/each} - - - {torrent.seasons} - {convertTorrentSeasonRangeToIntegerRange(torrent)} - - - - - - {/each} - - -
- {:else if show?.seasons?.length > 0} -

No torrents found for season {selectedSeasonNumber}. Try a different season.

- {/if} -
-
+ +

+ {@render saveDirectoryPreview(show, filePathSuffix)} +

+ {:else} +

+ No season information available for this show. +

+ {/if} +
+
+
+
+ {#if isLoadingTorrents} +
+ +

Loading torrents...

+
+ {:else if torrentsError} +

Error: {torrentsError}

+ {:else if torrents.length > 0} +

Found Torrents:

+
+ + + + Title + Size + Seeders + Indexer Flags + Seasons + Actions + + + + {#each torrents as torrent (torrent.id)} + + {torrent.title} + {(torrent.size / 1024 / 1024 / 1024).toFixed(2)}GB + {torrent.seeders} + + {#each torrent.flags as flag} + {flag},  + {/each} + + + {torrent.seasons} + {convertTorrentSeasonRangeToIntegerRange(torrent)} + + + + + + {/each} + + +
+ {:else if show?.seasons?.length > 0} +

No torrents found for season {selectedSeasonNumber}. Try a different season.

+ {/if} +
+
diff --git a/web/src/lib/components/login-form.svelte b/web/src/lib/components/login-form.svelte index a56d91c..9f9e0d7 100644 --- a/web/src/lib/components/login-form.svelte +++ b/web/src/lib/components/login-form.svelte @@ -1,208 +1,222 @@ + {#snippet tabSwitcher()} - - - - + + + + {/snippet} - - - - {@render tabSwitcher()} + + + + {@render tabSwitcher()} - Login - Enter your email below to login to your account - - -
-
- - -
-
-
- - - Forgot your password? -
- -
+ Login + Enter your email below to login to your account + + + +
+ + +
+
+ + +
- {#if errorMessage} -

{errorMessage}

- {/if} + {#if errorMessage} +

{errorMessage}

+ {/if} - - + + - + -
- Don't have an account? - Sign up -
-
-
-
- - +
+ +
+ +
+
+ + + + {@render tabSwitcher()} + Sign up + Enter your email and password below to sign up. + + +
+
+ + +
+
+
+ +
+ +
- - {@render tabSwitcher()} - Sign up - Enter your email and password below to sign up. - - + {#if errorMessage} +

{errorMessage}

+ {/if} - -
- - -
-
-
- -
- -
+ + - {#if errorMessage} -

{errorMessage}

- {/if} + - - - - - -
- Already have an account? - Login -
-
-
-
+
+ +
+ +
+
- - diff --git a/web/src/lib/components/logo-side-by-side.svelte b/web/src/lib/components/logo-side-by-side.svelte index 0805381..df1f0b7 100644 --- a/web/src/lib/components/logo-side-by-side.svelte +++ b/web/src/lib/components/logo-side-by-side.svelte @@ -5,6 +5,6 @@
- Logo + Logo Media Manager -
\ No newline at end of file + diff --git a/web/src/lib/components/nav-secondary.svelte b/web/src/lib/components/nav-secondary.svelte index 5e144fa..c755038 100644 --- a/web/src/lib/components/nav-secondary.svelte +++ b/web/src/lib/components/nav-secondary.svelte @@ -1,10 +1,10 @@ diff --git a/web/src/lib/components/request-season-dialog.svelte b/web/src/lib/components/request-season-dialog.svelte index 5e3c8bc..628957e 100644 --- a/web/src/lib/components/request-season-dialog.svelte +++ b/web/src/lib/components/request-season-dialog.svelte @@ -5,7 +5,7 @@ import {Label} from '$lib/components/ui/label'; import * as Select from '$lib/components/ui/select/index.js'; import LoaderCircle from '@lucide/svelte/icons/loader-circle'; - import type {PublicShow, Quality, CreateSeasonRequest} from '$lib/types.js'; + import type {CreateSeasonRequest, PublicShow, Quality} from '$lib/types.js'; import {getFullyQualifiedShowName, getTorrentQualityString} from '$lib/utils.js'; import {toast} from 'svelte-sonner'; @@ -20,19 +20,21 @@ const qualityValues: Quality[] = [1, 2, 3, 4]; let qualityOptions = $derived( - qualityValues.map(q => ({value: q, label: getTorrentQualityString(q)})) + qualityValues.map((q) => ({value: q, label: getTorrentQualityString(q)})) ); let isFormInvalid = $derived( - !selectedSeasonsIds || selectedSeasonsIds.length === 0 || - !minQuality || !wantedQuality || - (wantedQuality > minQuality) + !selectedSeasonsIds || + selectedSeasonsIds.length === 0 || + !minQuality || + !wantedQuality || + wantedQuality > minQuality ); async function handleRequestSeason() { isSubmittingRequest = true; submitRequestError = null; - const payloads: CreateSeasonRequest = selectedSeasonsIds.map(seasonId => ({ + const payloads: CreateSeasonRequest = selectedSeasonsIds.map((seasonId) => ({ season_id: seasonId, min_quality: minQuality, wanted_quality: wantedQuality @@ -42,13 +44,14 @@ const response = await fetch(`${env.PUBLIC_API_URL}/tv/seasons/requests`, { method: 'POST', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload) }); - if (response.status === 204) { // Success, no content + if (response.status === 204) { + // Success, no content dialogOpen = false; // Close the dialog // Reset form fields selectedSeasonsIds = undefined; @@ -70,15 +73,14 @@ } } } - { - dialogOpen = true; - }} + dialogOpen = true; + }} > Request Season @@ -96,8 +98,8 @@ {#each selectedSeasonsIds as seasonId (seasonId)} - {#if show.seasons.find(season => season.id === seasonId)} - {show.seasons.find(season => season.id === seasonId).number},  + {#if show.seasons.find((season) => season.id === seasonId)} + {show.seasons.find((season) => season.id === seasonId).number},  {/if} {:else} Select one or more seasons @@ -118,7 +120,7 @@ - {minQuality ? getTorrentQualityString(minQuality) : "Select Minimum Quality"} + {minQuality ? getTorrentQualityString(minQuality) : 'Select Minimum Quality'} {#each qualityOptions as option (option.value)} @@ -133,7 +135,7 @@ - {wantedQuality ? getTorrentQualityString(wantedQuality) : "Select Wanted Quality"} + {wantedQuality ? getTorrentQualityString(wantedQuality) : 'Select Wanted Quality'} {#each qualityOptions as option (option.value)} @@ -144,15 +146,15 @@ {#if submitRequestError} -

{submitRequestError}

+

{submitRequestError}

{/if} - - + {/if} {#if user().is_superuser || user().id === request.requested_by?.id} - {/if} diff --git a/web/src/lib/components/torrent-table.svelte b/web/src/lib/components/torrent-table.svelte index 3651020..b8073b8 100644 --- a/web/src/lib/components/torrent-table.svelte +++ b/web/src/lib/components/torrent-table.svelte @@ -3,12 +3,11 @@ convertTorrentSeasonRangeToIntegerRange, getTorrentQualityString, getTorrentStatusString - } from "$lib/utils.js"; - import CheckmarkX from "$lib/components/checkmark-x.svelte"; - import * as Table from "$lib/components/ui/table/index.js"; + } from '$lib/utils.js'; + import CheckmarkX from '$lib/components/checkmark-x.svelte'; + import * as Table from '$lib/components/ui/table/index.js'; let {torrents} = $props(); - @@ -59,4 +58,4 @@ {/each} - \ No newline at end of file + diff --git a/web/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte b/web/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte index e5369c5..ba4023a 100644 --- a/web/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +++ b/web/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte @@ -1,8 +1,8 @@