migrate all .svelte files to new types created with openapi-ts

This commit is contained in:
maxDorninger
2025-09-12 19:58:53 +02:00
parent 3eaa3dd233
commit d6c1a03d78
14 changed files with 238 additions and 240 deletions

View File

@@ -5,7 +5,6 @@
import { cn } from '$lib/utils.js';
import { tick } from 'svelte';
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-svelte';
import type { PublicMovie, PublicShow } from '$lib/types.js';
import { onMount } from 'svelte';
import { toast } from 'svelte-sonner';
import client from '$lib/api';
@@ -15,7 +14,7 @@
media,
mediaType
}: {
media: PublicShow | PublicMovie;
media: components["schemas"]["PublicShow"] | components["schemas"]["PublicMovie"];
mediaType: 'tv' | 'movie';
} = $props();
@@ -31,9 +30,9 @@
const movieLibraries = await client.GET('/api/v1/movies/libraries');
if (mediaType === 'tv') {
libraries = tvLibraries.data;
libraries = tvLibraries.data as components['schemas']['LibraryItem'][];
} else {
libraries = movieLibraries.data;
libraries = movieLibraries.data as components['schemas']['LibraryItem'][];
}
if (!value && libraries.length > 0) {
@@ -53,14 +52,14 @@
if (mediaType === 'tv') {
response = await client.POST('/api/v1/tv/shows/{show_id}/library', {
params: {
path: { show_id: media.id },
path: { show_id: media.id! },
query: { library: selectedLabel }
}
});
} else {
response = await client.POST('/api/v1/movies/{movie_id}/library', {
params: {
path: { movie_id: media.id },
path: { movie_id: media.id! },
query: { library: selectedLabel }
}
});

View File

@@ -11,7 +11,6 @@
import LoadingBar from '$lib/components/loading-bar.svelte';
import client from '$lib/api';
import {handleOauth} from "$lib/utils.ts";
import type {components} from "$lib/api/api";
let {
oauthProviderNames

View File

@@ -4,19 +4,19 @@
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 { PublicMovie, Quality } from '$lib/types.js';
import { getFullyQualifiedMediaName, getTorrentQualityString } from '$lib/utils.js';
import { toast } from 'svelte-sonner';
import client from '$lib/api';
import type {components} from "$lib/api/api";
let { movie }: { movie: PublicMovie } = $props();
let { movie }: { movie: components["schemas"]["PublicMovie"] } = $props();
let dialogOpen = $state(false);
let minQuality = $state<string | undefined>(undefined);
let wantedQuality = $state<string | undefined>(undefined);
let isSubmittingRequest = $state(false);
let submitRequestError = $state<string | null>(null);
const qualityValues: Quality[] = [1, 2, 3, 4];
const qualityValues: components["schemas"]["Quality"][] = [1, 2, 3, 4];
let qualityOptions = $derived(
qualityValues.map((q) => ({ value: q.toString(), label: getTorrentQualityString(q) }))
);
@@ -29,9 +29,9 @@
submitRequestError = null;
const { response } = await client.POST('/api/v1/movies/requests', {
body: {
movie_id: movie.id,
min_quality: parseInt(minQuality!),
wanted_quality: parseInt(wantedQuality!)
movie_id: movie.id!,
min_quality: parseInt(minQuality!) as components["schemas"]["Quality"],
wanted_quality: parseInt(wantedQuality!) as components["schemas"]["Quality"]
}
});
isSubmittingRequest = false;

View File

@@ -4,12 +4,12 @@
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 } from '$lib/types.js';
import { getFullyQualifiedMediaName, getTorrentQualityString } from '$lib/utils.js';
import { toast } from 'svelte-sonner';
import client from '$lib/api';
import type {components} from "$lib/api/api";
let { show }: { show: PublicShow } = $props();
let { show }: { show: components["schemas"]["PublicShow"] } = $props();
let dialogOpen = $state(false);
let selectedSeasonsIds = $state<string[]>([]);
@@ -18,7 +18,7 @@
let isSubmittingRequest = $state(false);
let submitRequestError = $state<string | null>(null);
const qualityValues: Quality[] = [1, 2, 3, 4];
const qualityValues: components["schemas"]["Quality"][] = [1, 2, 3, 4];
let qualityOptions = $derived(
qualityValues.map((q) => ({ value: q.toString(), label: getTorrentQualityString(q) }))
);
@@ -38,8 +38,8 @@
const { response, error } = await client.POST('/api/v1/tv/seasons/requests', {
body: {
season_id: id,
min_quality: parseInt(minQuality!) as Quality,
wanted_quality: parseInt(wantedQuality!) as Quality
min_quality: parseInt(minQuality!) as components["schemas"]["Quality"],
wanted_quality: parseInt(wantedQuality!) as components["schemas"]["Quality"]
}
});

View File

@@ -1,202 +1,203 @@
<script lang="ts">
import { getFullyQualifiedMediaName, getTorrentQualityString } from '$lib/utils.js';
import type { MovieRequest, SeasonRequest, User } from '$lib/types.js';
import CheckmarkX from '$lib/components/checkmark-x.svelte';
import * as Table from '$lib/components/ui/table/index.js';
import { getContext } from 'svelte';
import { Button } from '$lib/components/ui/button/index.js';
import { toast } from 'svelte-sonner';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import client from '$lib/api';
import {getFullyQualifiedMediaName, getTorrentQualityString} from '$lib/utils.js';
import CheckmarkX from '$lib/components/checkmark-x.svelte';
import type {components} from '$lib/api/api';
let {
requests,
filter = () => true,
isShow = true
}: {
requests: (SeasonRequest | MovieRequest)[];
filter?: (request: SeasonRequest | MovieRequest) => boolean;
isShow: boolean;
} = $props();
const user: () => User = getContext('user');
import * as Table from '$lib/components/ui/table/index.js';
import {getContext} from 'svelte';
import {Button} from '$lib/components/ui/button/index.js';
import {toast} from 'svelte-sonner';
import {goto} from '$app/navigation';
import {base} from '$app/paths';
import client from '$lib/api';
async function approveRequest(requestId: string, currentAuthorizedStatus: boolean) {
let response;
if (isShow) {
const data = await client.PATCH('/api/v1/tv/seasons/requests/{season_request_id}', {
params: {
path: {
season_request_id: requestId
},
query: {
authorized_status: !currentAuthorizedStatus
}
}
});
response = data.response;
} else {
const data = await client.PATCH('/api/v1/movies/requests/{movie_request_id}', {
params: {
path: {
movie_request_id: requestId
},
query: {
authorized_status: !currentAuthorizedStatus
}
}
});
response = data.response;
}
if (response.ok) {
const requestIndex = requests.findIndex((r) => r.id === requestId);
if (requestIndex !== -1) {
let newAuthorizedStatus = !currentAuthorizedStatus;
requests[requestIndex]!.authorized = newAuthorizedStatus;
requests[requestIndex]!.authorized_by = newAuthorizedStatus ? user() : undefined;
}
toast.success(
`Request ${!currentAuthorizedStatus ? 'approved' : 'unapproved'} successfully.`
);
} else {
const errorText = await response.text();
console.error(`Failed to update request status ${response.statusText}`, errorText);
toast.error(`Failed to update request status: ${response.statusText}`);
}
}
let {
requests,
filter = () => true,
isShow = true
}: {
requests: (components["schemas"]["RichSeasonRequest"] | components["schemas"]["RichMovieRequest"])[];
filter?: (request: components["schemas"]["RichSeasonRequest"] | components["schemas"]["RichMovieRequest"]) => boolean;
isShow: boolean;
} = $props();
const user: () => components["schemas"]["UserRead"] = getContext('user');
async function deleteRequest(requestId: string) {
if (
!window.confirm(
'Are you sure you want to delete this season request? This action cannot be undone.'
)
) {
return;
}
let response;
if (isShow) {
const data = await client.DELETE('/api/v1/tv/seasons/requests/{request_id}', {
params: {
path: {
request_id: requestId
}
}
});
response = data.response;
} else {
const data = await client.DELETE('/api/v1/movies/requests/{movie_request_id}', {
params: {
path: {
movie_request_id: requestId
}
}
});
response = data.response;
}
if (response.ok) {
// remove the request from the list
const index = requests.findIndex((r) => r.id === requestId);
if (index > -1) {
requests.splice(index, 1);
}
toast.success('Request deleted successfully');
} else {
console.error(`Failed to delete request ${response.statusText}`, await response.text());
toast.error('Failed to delete request');
}
}
async function approveRequest(requestId: string, currentAuthorizedStatus: boolean) {
let response;
if (isShow) {
const data = await client.PATCH('/api/v1/tv/seasons/requests/{season_request_id}', {
params: {
path: {
season_request_id: requestId
},
query: {
authorized_status: !currentAuthorizedStatus
}
}
});
response = data.response;
} else {
const data = await client.PATCH('/api/v1/movies/requests/{movie_request_id}', {
params: {
path: {
movie_request_id: requestId
},
query: {
authorized_status: !currentAuthorizedStatus
}
}
});
response = data.response;
}
if (response.ok) {
const requestIndex = requests.findIndex((r) => r.id === requestId);
if (requestIndex !== -1) {
let newAuthorizedStatus = !currentAuthorizedStatus;
requests[requestIndex]!.authorized = newAuthorizedStatus;
requests[requestIndex]!.authorized_by = newAuthorizedStatus ? user() : undefined;
}
toast.success(
`Request ${!currentAuthorizedStatus ? 'approved' : 'unapproved'} successfully.`
);
} else {
const errorText = await response.text();
console.error(`Failed to update request status ${response.statusText}`, errorText);
toast.error(`Failed to update request status: ${response.statusText}`);
}
}
async function deleteRequest(requestId: string) {
if (
!window.confirm(
'Are you sure you want to delete this season request? This action cannot be undone.'
)
) {
return;
}
let response;
if (isShow) {
const data = await client.DELETE('/api/v1/tv/seasons/requests/{request_id}', {
params: {
path: {
request_id: requestId
}
}
});
response = data.response;
} else {
const data = await client.DELETE('/api/v1/movies/requests/{movie_request_id}', {
params: {
path: {
movie_request_id: requestId
}
}
});
response = data.response;
}
if (response.ok) {
// remove the request from the list
const index = requests.findIndex((r) => r.id === requestId);
if (index > -1) {
requests.splice(index, 1);
}
toast.success('Request deleted successfully');
} else {
console.error(`Failed to delete request ${response.statusText}`, await response.text());
toast.error('Failed to delete request');
}
}
</script>
<Table.Root>
<Table.Caption>A list of all requests.</Table.Caption>
<Table.Header>
<Table.Row>
<Table.Head>{isShow ? 'Show' : 'Movie'}</Table.Head>
{#if isShow}
<Table.Head>Season</Table.Head>
{/if}
<Table.Head>Minimum Quality</Table.Head>
<Table.Head>Wanted Quality</Table.Head>
<Table.Head>Requested by</Table.Head>
<Table.Head>Approved</Table.Head>
<Table.Head>Approved by</Table.Head>
<Table.Head>Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each requests as request (request.id)}
{#if filter(request)}
<Table.Row>
<Table.Cell>
{#if isShow}
{getFullyQualifiedMediaName((request as SeasonRequest).show)}
{:else}
{getFullyQualifiedMediaName((request as MovieRequest).movie)}
{/if}
</Table.Cell>
{#if isShow}
<Table.Cell>
{(request as SeasonRequest).season.number}
</Table.Cell>
{/if}
<Table.Cell>
{getTorrentQualityString(request.min_quality)}
</Table.Cell>
<Table.Cell>
{getTorrentQualityString(request.wanted_quality)}
</Table.Cell>
<Table.Cell>
{request.requested_by?.email ?? 'N/A'}
</Table.Cell>
<Table.Cell>
<CheckmarkX state={request.authorized} />
</Table.Cell>
<Table.Cell>
{request.authorized_by?.email ?? 'N/A'}
</Table.Cell>
<!-- TODO: ADD DIALOGUE TO MODIFY REQUEST -->
<Table.Cell class="flex max-w-[150px] flex-col gap-1">
{#if user().is_superuser}
<Button
class=""
size="sm"
onclick={() => approveRequest(request.id, request.authorized)}
>
{request.authorized ? 'Unapprove' : 'Approve'}
</Button>
{#if isShow}
<Button
class=""
size="sm"
variant="outline"
onclick={() => goto(base + '/dashboard/tv/' + (request as SeasonRequest).show.id)}
>
Download manually
</Button>
{:else}
<Button
class=""
size="sm"
variant="outline"
onclick={() =>
goto(base + '/dashboard/movies/' + (request as MovieRequest).movie.id)}
>
Download manually
</Button>
{/if}
{/if}
{#if user().is_superuser || user().id === request.requested_by?.id}
<Button variant="destructive" size="sm" onclick={() => deleteRequest(request.id)}
>Delete
</Button>
{/if}
</Table.Cell>
</Table.Row>
{/if}
{:else}
<Table.Row>
<Table.Cell colspan={8} class="text-center">There are currently no requests.</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
<Table.Caption>A list of all requests.</Table.Caption>
<Table.Header>
<Table.Row>
<Table.Head>{isShow ? 'Show' : 'Movie'}</Table.Head>
{#if isShow}
<Table.Head>Season</Table.Head>
{/if}
<Table.Head>Minimum Quality</Table.Head>
<Table.Head>Wanted Quality</Table.Head>
<Table.Head>Requested by</Table.Head>
<Table.Head>Approved</Table.Head>
<Table.Head>Approved by</Table.Head>
<Table.Head>Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each requests as request (request.id)}
{#if filter(request)}
<Table.Row>
<Table.Cell>
{#if isShow}
{getFullyQualifiedMediaName((request as components["schemas"]["RichSeasonRequest"]).show)}
{:else}
{getFullyQualifiedMediaName((request as components["schemas"]["RichMovieRequest"]).movie)}
{/if}
</Table.Cell>
{#if isShow}
<Table.Cell>
{(request as components["schemas"]["RichSeasonRequest"]).season.number}
</Table.Cell>
{/if}
<Table.Cell>
{getTorrentQualityString(request.min_quality)}
</Table.Cell>
<Table.Cell>
{getTorrentQualityString(request.wanted_quality)}
</Table.Cell>
<Table.Cell>
{request.requested_by?.email ?? 'N/A'}
</Table.Cell>
<Table.Cell>
<CheckmarkX state={request.authorized}/>
</Table.Cell>
<Table.Cell>
{request.authorized_by?.email ?? 'N/A'}
</Table.Cell>
<!-- TODO: ADD DIALOGUE TO MODIFY REQUEST -->
<Table.Cell class="flex max-w-[150px] flex-col gap-1">
{#if user().is_superuser}
<Button
class=""
size="sm"
onclick={() => approveRequest(request.id!, request.authorized)}
>
{request.authorized ? 'Unapprove' : 'Approve'}
</Button>
{#if isShow}
<Button
class=""
size="sm"
variant="outline"
onclick={() => goto(base + '/dashboard/tv/' + (request as components["schemas"]["RichSeasonRequest"]).show.id)}
>
Download manually
</Button>
{:else}
<Button
class=""
size="sm"
variant="outline"
onclick={() =>
goto(base + '/dashboard/movies/' + (request as components["schemas"]["RichMovieRequest"]).movie.id)}
>
Download manually
</Button>
{/if}
{/if}
{#if user().is_superuser || user().id === request.requested_by?.id}
<Button variant="destructive" size="sm" onclick={() => deleteRequest(request.id!)}
>Delete
</Button>
{/if}
</Table.Cell>
</Table.Row>
{/if}
{:else}
<Table.Row>
<Table.Cell colspan={8} class="text-center">There are currently no requests.</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>

View File

@@ -1,5 +1,4 @@
<script lang="ts">
import type { User } from '$lib/types.js';
import CheckmarkX from '$lib/components/checkmark-x.svelte';
import * as Table from '$lib/components/ui/table/index.js';
import { Button } from '$lib/components/ui/button/index.js';
@@ -10,10 +9,11 @@
import { Input } from '$lib/components/ui/input/index.js';
import { invalidateAll } from '$app/navigation';
import client from '$lib/api';
import type {components} from "$lib/api/api";
let { users }: { users: User[] } = $props();
let { users }: { users: components["schemas"]["UserRead"] [] } = $props();
let sortedUsers = $derived(users.sort((a, b) => a.email.localeCompare(b.email)));
let selectedUser: User | null = $state(null);
let selectedUser: components["schemas"]["UserRead"] | null = $state(null);
let newPassword: string = $state('');
let newEmail: string = $state('');
let dialogOpen = $state(false);

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import { getContext } from 'svelte';
import type { User } from '$lib/types';
const user: () => User = getContext('user');
import type {components} from "$lib/api/api";
const user: () => components["schemas"]["UserRead"] = getContext('user');
</script>
<span class="truncate font-semibold">{user().email}</span>