diff --git a/.gitignore b/.gitignore index bbf5f02..003a3b7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ vite.config.ts.timestamp-* .idea /.vs /package-lock.json +.vscode +server-config.json \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 9dd507f..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "command": "bun dev", - "name": "Run Riven Frontend", - "request": "launch", - "type": "node-terminal" - } - ] -} diff --git a/package.json b/package.json index 53cb3e8..6a9452d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "test": "vitest", "lint": "prettier --check . && eslint .", - "format": "prettier --write .", + "format": "prettier --write", + "format-all": "prettier --write .", "generate-client": "openapi-ts generate" }, "devDependencies": { diff --git a/src/app.d.ts b/src/app.d.ts index 27a3c6d..fa2833a 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,12 +1,9 @@ declare global { namespace App { - // interface Error {} interface Locals { - BACKEND_URL: string; + backendUrl: string; + apiKey: string; } - // interface PageData {} - // interface PageState {} - // interface Platform {} } } diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 8b6a188..c58c4af 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,7 +1,7 @@ import { client } from '$lib/client/services.gen'; client.setConfig({ - baseUrl: '/api' + baseUrl: '' }); client.interceptors.error.use((error: unknown) => { diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 66759dc..6dd0aaf 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,41 +1,55 @@ import type { Handle } from '@sveltejs/kit'; -import { redirect, error } from '@sveltejs/kit'; +import { redirect } from '@sveltejs/kit'; import { sequence } from '@sveltejs/kit/hooks'; -import { env } from '$env/dynamic/private'; -const BACKEND_URL = env.BACKEND_URL || 'http://127.0.0.1:8080'; -import { client, DefaultService } from '$lib/client/services.gen'; +import { client } from '$lib/client/services.gen'; +import { getServerConfig } from '$lib/serverConfig'; -const setLocals: Handle = async ({ event, resolve }) => { - event.locals.BACKEND_URL = BACKEND_URL; +const configureClientMiddleware: Handle = async ({ event, resolve }) => { + const config = await getServerConfig(); - return resolve(event); -}; + if (config) { + event.locals.backendUrl = config.backendUrl; + event.locals.apiKey = config.apiKey; + client.setConfig({ + baseUrl: config.backendUrl, + headers: { + 'x-api-key': config.apiKey + } + }); + } -const onboarding: Handle = async ({ event, resolve }) => { - if (!(event.url.pathname.startsWith('/onboarding') || event.url.pathname.startsWith('/api')) && event.request.method === 'GET') { - const { data, error: apiError } = await DefaultService.services(); - if (apiError || !data) { - return error(500, 'API Error'); - } - const toCheck = ['symlink', 'symlinklibrary']; - const allServicesTrue: boolean = toCheck.every((service) => data[service] === true); - if (!allServicesTrue) { - redirect(302, '/onboarding'); + if ( + !event.url.pathname.startsWith('/connect') && + !event.url.pathname.startsWith('/api/configure-client') && + event.request.method === 'GET' + ) { + if (!event.locals.backendUrl || !event.locals.apiKey) { + throw redirect(307, '/connect'); } } return resolve(event); }; -client.setConfig({ - baseUrl: BACKEND_URL -}); +const errorInterceptor: Handle = async ({ event, resolve }) => { + const response = await resolve(event); -client.interceptors.error.use((error: unknown) => { - if (error && typeof error == 'object' && 'detail' in error && typeof error.detail == 'string') { - return error.detail; - } - return undefined; -}); + client.interceptors.error.use((error: unknown) => { + if ( + error && + typeof error === 'object' && + 'detail' in error && + typeof error.detail === 'string' + ) { + if (error.detail === 'Missing or invalid API key') { + throw redirect(307, '/connect'); + } + return error.detail; + } + return undefined; + }); + + return response; +}; -export const handle = sequence(setLocals, onboarding); +export const handle = sequence(configureClientMiddleware, errorInterceptor); diff --git a/src/lib/client/schemas.gen.ts b/src/lib/client/schemas.gen.ts index 4c2363e..19642cf 100644 --- a/src/lib/client/schemas.gen.ts +++ b/src/lib/client/schemas.gen.ts @@ -64,7 +64,12 @@ export const AppModelSchema = { version: { type: 'string', title: 'Version', - default: '0.16.0' + default: '0.15.3' + }, + api_key: { + type: 'string', + title: 'Api Key', + default: '' }, debug: { type: 'boolean', diff --git a/src/lib/client/services.gen.ts b/src/lib/client/services.gen.ts index 08438a2..04fdd83 100644 --- a/src/lib/client/services.gen.ts +++ b/src/lib/client/services.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -import type { RootError, RootResponse2, HealthError, HealthResponse, RdError, RdResponse, TorboxError, TorboxResponse, ServicesError, ServicesResponse, TraktOauthInitiateError, TraktOauthInitiateResponse, TraktOauthCallbackData, TraktOauthCallbackError, TraktOauthCallbackResponse, StatsError, StatsResponse2, LogsError, LogsResponse, EventsError, EventsResponse, MountError, MountResponse, OverseerrWebhookOverseerrPostError, OverseerrWebhookOverseerrPostResponse, GetSettingsSchemaError, GetSettingsSchemaResponse, LoadSettingsError, LoadSettingsResponse, SaveSettingsError, SaveSettingsResponse, GetAllSettingsError, GetAllSettingsResponse, GetSettingsData, GetSettingsError, GetSettingsResponse, SetAllSettingsData, SetAllSettingsError, SetAllSettingsResponse, SetSettingsData, SetSettingsError, SetSettingsResponse, GetStatesError, GetStatesResponse, GetItemsData, GetItemsError, GetItemsResponse, AddItemsData, AddItemsError, AddItemsResponse, GetItemData, GetItemError, GetItemResponse, GetItemsByImdbIdsData, GetItemsByImdbIdsError, GetItemsByImdbIdsResponse, ResetItemsData, ResetItemsError, ResetItemsResponse, RetryItemsData, RetryItemsError, RetryItemsResponse, RemoveItemData, RemoveItemError, RemoveItemResponse, SetTorrentRdMagnetData, SetTorrentRdMagnetError, SetTorrentRdMagnetResponse, SetTorrentRdItemsIdSetTorrentRdPostData, SetTorrentRdItemsIdSetTorrentRdPostError, SetTorrentRdItemsIdSetTorrentRdPostResponse, ScrapeData, ScrapeError, ScrapeResponse, GetRdTorrentsData, GetRdTorrentsError, GetRdTorrentsResponse, GetTrendingData, GetTrendingError, GetTrendingResponse, GetMoviesNowPlayingData, GetMoviesNowPlayingError, GetMoviesNowPlayingResponse, GetMoviesPopularData, GetMoviesPopularError, GetMoviesPopularResponse, GetMoviesTopRatedData, GetMoviesTopRatedError, GetMoviesTopRatedResponse, GetMoviesUpcomingData, GetMoviesUpcomingError, GetMoviesUpcomingResponse, GetMovieDetailsData, GetMovieDetailsError, GetMovieDetailsResponse, GetTvAiringTodayData, GetTvAiringTodayError, GetTvAiringTodayResponse, GetTvOnTheAirData, GetTvOnTheAirError, GetTvOnTheAirResponse, GetTvPopularData, GetTvPopularError, GetTvPopularResponse, GetTvTopRatedData, GetTvTopRatedError, GetTvTopRatedResponse, GetTvDetailsData, GetTvDetailsError, GetTvDetailsResponse, GetTvSeasonDetailsData, GetTvSeasonDetailsError, GetTvSeasonDetailsResponse, GetTvEpisodeDetailsData, GetTvEpisodeDetailsError, GetTvEpisodeDetailsResponse, SearchCollectionData, SearchCollectionError, SearchCollectionResponse, SearchMovieData, SearchMovieError, SearchMovieResponse, SearchMultiData, SearchMultiError, SearchMultiResponse, SearchTvData, SearchTvError, SearchTvResponse, GetFromExternalIdData, GetFromExternalIdError, GetFromExternalIdResponse } from './types.gen'; +import type { RootError, RootResponse2, HealthError, HealthResponse, RdError, RdResponse, GenerateapikeyError, GenerateapikeyResponse, TorboxError, TorboxResponse, ServicesError, ServicesResponse, TraktOauthInitiateError, TraktOauthInitiateResponse, TraktOauthCallbackData, TraktOauthCallbackError, TraktOauthCallbackResponse, StatsError, StatsResponse2, LogsError, LogsResponse, EventsError, EventsResponse, MountError, MountResponse, OverseerrApiV1WebhookOverseerrPostError, OverseerrApiV1WebhookOverseerrPostResponse, GetStatesError, GetStatesResponse, GetItemsData, GetItemsError, GetItemsResponse, AddItemsData, AddItemsError, AddItemsResponse, GetItemData, GetItemError, GetItemResponse, GetItemsByImdbIdsData, GetItemsByImdbIdsError, GetItemsByImdbIdsResponse, ResetItemsData, ResetItemsError, ResetItemsResponse, RetryItemsData, RetryItemsError, RetryItemsResponse, RemoveItemData, RemoveItemError, RemoveItemResponse, SetTorrentRdMagnetData, SetTorrentRdMagnetError, SetTorrentRdMagnetResponse, SetTorrentRdApiV1ItemsIdSetTorrentRdPostData, SetTorrentRdApiV1ItemsIdSetTorrentRdPostError, SetTorrentRdApiV1ItemsIdSetTorrentRdPostResponse, ScrapeData, ScrapeError, ScrapeResponse, GetRdTorrentsData, GetRdTorrentsError, GetRdTorrentsResponse, GetSettingsSchemaError, GetSettingsSchemaResponse, LoadSettingsError, LoadSettingsResponse, SaveSettingsError, SaveSettingsResponse, GetAllSettingsError, GetAllSettingsResponse, GetSettingsData, GetSettingsError, GetSettingsResponse, SetAllSettingsData, SetAllSettingsError, SetAllSettingsResponse, SetSettingsData, SetSettingsError, SetSettingsResponse, GetTrendingData, GetTrendingError, GetTrendingResponse, GetMoviesNowPlayingData, GetMoviesNowPlayingError, GetMoviesNowPlayingResponse, GetMoviesPopularData, GetMoviesPopularError, GetMoviesPopularResponse, GetMoviesTopRatedData, GetMoviesTopRatedError, GetMoviesTopRatedResponse, GetMoviesUpcomingData, GetMoviesUpcomingError, GetMoviesUpcomingResponse, GetMovieDetailsData, GetMovieDetailsError, GetMovieDetailsResponse, GetTvAiringTodayData, GetTvAiringTodayError, GetTvAiringTodayResponse, GetTvOnTheAirData, GetTvOnTheAirError, GetTvOnTheAirResponse, GetTvPopularData, GetTvPopularError, GetTvPopularResponse, GetTvTopRatedData, GetTvTopRatedError, GetTvTopRatedResponse, GetTvDetailsData, GetTvDetailsError, GetTvDetailsResponse, GetTvSeasonDetailsData, GetTvSeasonDetailsError, GetTvSeasonDetailsResponse, GetTvEpisodeDetailsData, GetTvEpisodeDetailsError, GetTvEpisodeDetailsResponse, SearchCollectionData, SearchCollectionError, SearchCollectionResponse, SearchMovieData, SearchMovieError, SearchMovieResponse, SearchMultiData, SearchMultiError, SearchMultiResponse, SearchTvData, SearchTvError, SearchTvResponse, GetFromExternalIdData, GetFromExternalIdError, GetFromExternalIdResponse } from './types.gen'; export const client = createClient(createConfig()); @@ -12,7 +12,7 @@ export class DefaultService { public static root(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/' + url: '/api/v1/' }); } @@ -22,7 +22,7 @@ export class DefaultService { public static health(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/health' + url: '/api/v1/health' }); } @@ -32,7 +32,17 @@ export class DefaultService { public static rd(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/rd' + url: '/api/v1/rd' + }); + } + + /** + * Generate Apikey + */ + public static generateapikey(options?: Options) { + return (options?.client ?? client).post({ + ...options, + url: '/api/v1/generateapikey' }); } @@ -42,7 +52,7 @@ export class DefaultService { public static torbox(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/torbox' + url: '/api/v1/torbox' }); } @@ -52,7 +62,7 @@ export class DefaultService { public static services(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/services' + url: '/api/v1/services' }); } @@ -62,7 +72,7 @@ export class DefaultService { public static traktOauthInitiate(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/trakt/oauth/initiate' + url: '/api/v1/trakt/oauth/initiate' }); } @@ -72,7 +82,7 @@ export class DefaultService { public static traktOauthCallback(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/trakt/oauth/callback' + url: '/api/v1/trakt/oauth/callback' }); } @@ -82,7 +92,7 @@ export class DefaultService { public static stats(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/stats' + url: '/api/v1/stats' }); } @@ -92,7 +102,7 @@ export class DefaultService { public static logs(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/logs' + url: '/api/v1/logs' }); } @@ -102,7 +112,7 @@ export class DefaultService { public static events(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/events' + url: '/api/v1/events' }); } @@ -113,7 +123,7 @@ export class DefaultService { public static mount(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/mount' + url: '/api/v1/mount' }); } @@ -121,84 +131,10 @@ export class DefaultService { * Overseerr * Webhook for Overseerr */ - public static overseerrWebhookOverseerrPost(options?: Options) { - return (options?.client ?? client).post({ - ...options, - url: '/webhook/overseerr' - }); - } - -} - -export class SettingsService { - /** - * Get Settings Schema - * Get the JSON schema for the settings. - */ - public static getSettingsSchema(options?: Options) { - return (options?.client ?? client).get({ - ...options, - url: '/settings/schema' - }); - } - - /** - * Load Settings - */ - public static loadSettings(options?: Options) { - return (options?.client ?? client).get({ - ...options, - url: '/settings/load' - }); - } - - /** - * Save Settings - */ - public static saveSettings(options?: Options) { - return (options?.client ?? client).post({ - ...options, - url: '/settings/save' - }); - } - - /** - * Get All Settings - */ - public static getAllSettings(options?: Options) { - return (options?.client ?? client).get({ - ...options, - url: '/settings/get/all' - }); - } - - /** - * Get Settings - */ - public static getSettings(options: Options) { - return (options?.client ?? client).get({ - ...options, - url: '/settings/get/{paths}' - }); - } - - /** - * Set All Settings - */ - public static setAllSettings(options: Options) { - return (options?.client ?? client).post({ - ...options, - url: '/settings/set/all' - }); - } - - /** - * Set Settings - */ - public static setSettings(options: Options) { - return (options?.client ?? client).post({ + public static overseerrApiV1WebhookOverseerrPost(options?: Options) { + return (options?.client ?? client).post({ ...options, - url: '/settings/set' + url: '/api/v1/webhook/overseerr' }); } @@ -211,7 +147,7 @@ export class ItemsService { public static getStates(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/items/states' + url: '/api/v1/items/states' }); } @@ -222,7 +158,7 @@ export class ItemsService { public static getItems(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/items' + url: '/api/v1/items' }); } @@ -233,7 +169,7 @@ export class ItemsService { public static addItems(options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/items/add' + url: '/api/v1/items/add' }); } @@ -244,7 +180,7 @@ export class ItemsService { public static getItem(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/items/{id}' + url: '/api/v1/items/{id}' }); } @@ -255,7 +191,7 @@ export class ItemsService { public static getItemsByImdbIds(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/items/{imdb_ids}' + url: '/api/v1/items/{imdb_ids}' }); } @@ -266,7 +202,7 @@ export class ItemsService { public static resetItems(options: Options) { return (options?.client ?? client).post({ ...options, - url: '/items/reset' + url: '/api/v1/items/reset' }); } @@ -277,7 +213,7 @@ export class ItemsService { public static retryItems(options: Options) { return (options?.client ?? client).post({ ...options, - url: '/items/retry' + url: '/api/v1/items/retry' }); } @@ -288,7 +224,7 @@ export class ItemsService { public static removeItem(options: Options) { return (options?.client ?? client).delete({ ...options, - url: '/items/remove' + url: '/api/v1/items/remove' }); } @@ -299,7 +235,7 @@ export class ItemsService { public static setTorrentRdMagnet(options: Options) { return (options?.client ?? client).post({ ...options, - url: '/items/{id}/set_torrent_rd_magnet' + url: '/api/v1/items/{id}/set_torrent_rd_magnet' }); } @@ -307,10 +243,10 @@ export class ItemsService { * Set Torrent Rd * Set a torrent for a media item using RD torrent ID. */ - public static setTorrentRdItemsIdSetTorrentRdPost(options: Options) { - return (options?.client ?? client).post({ + public static setTorrentRdApiV1ItemsIdSetTorrentRdPost(options: Options) { + return (options?.client ?? client).post({ ...options, - url: '/items/{id}/set_torrent_rd' + url: '/api/v1/items/{id}/set_torrent_rd' }); } @@ -324,7 +260,7 @@ export class ScrapeService { public static scrape(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/scrape' + url: '/api/v1/scrape' }); } @@ -335,7 +271,81 @@ export class ScrapeService { public static getRdTorrents(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/scrape/rd' + url: '/api/v1/scrape/rd' + }); + } + +} + +export class SettingsService { + /** + * Get Settings Schema + * Get the JSON schema for the settings. + */ + public static getSettingsSchema(options?: Options) { + return (options?.client ?? client).get({ + ...options, + url: '/api/v1/settings/schema' + }); + } + + /** + * Load Settings + */ + public static loadSettings(options?: Options) { + return (options?.client ?? client).get({ + ...options, + url: '/api/v1/settings/load' + }); + } + + /** + * Save Settings + */ + public static saveSettings(options?: Options) { + return (options?.client ?? client).post({ + ...options, + url: '/api/v1/settings/save' + }); + } + + /** + * Get All Settings + */ + public static getAllSettings(options?: Options) { + return (options?.client ?? client).get({ + ...options, + url: '/api/v1/settings/get/all' + }); + } + + /** + * Get Settings + */ + public static getSettings(options: Options) { + return (options?.client ?? client).get({ + ...options, + url: '/api/v1/settings/get/{paths}' + }); + } + + /** + * Set All Settings + */ + public static setAllSettings(options: Options) { + return (options?.client ?? client).post({ + ...options, + url: '/api/v1/settings/set/all' + }); + } + + /** + * Set Settings + */ + public static setSettings(options: Options) { + return (options?.client ?? client).post({ + ...options, + url: '/api/v1/settings/set' }); } @@ -348,7 +358,7 @@ export class TmdbService { public static getTrending(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/trending/{type}/{window}' + url: '/api/v1/tmdb/trending/{type}/{window}' }); } @@ -358,7 +368,7 @@ export class TmdbService { public static getMoviesNowPlaying(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/movie/now_playing' + url: '/api/v1/tmdb/movie/now_playing' }); } @@ -368,7 +378,7 @@ export class TmdbService { public static getMoviesPopular(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/movie/popular' + url: '/api/v1/tmdb/movie/popular' }); } @@ -378,7 +388,7 @@ export class TmdbService { public static getMoviesTopRated(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/movie/top_rated' + url: '/api/v1/tmdb/movie/top_rated' }); } @@ -388,7 +398,7 @@ export class TmdbService { public static getMoviesUpcoming(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/movie/upcoming' + url: '/api/v1/tmdb/movie/upcoming' }); } @@ -398,7 +408,7 @@ export class TmdbService { public static getMovieDetails(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/movie/{movie_id}' + url: '/api/v1/tmdb/movie/{movie_id}' }); } @@ -408,7 +418,7 @@ export class TmdbService { public static getTvAiringToday(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/tv/airing_today' + url: '/api/v1/tmdb/tv/airing_today' }); } @@ -418,7 +428,7 @@ export class TmdbService { public static getTvOnTheAir(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/tv/on_the_air' + url: '/api/v1/tmdb/tv/on_the_air' }); } @@ -428,7 +438,7 @@ export class TmdbService { public static getTvPopular(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/tv/popular' + url: '/api/v1/tmdb/tv/popular' }); } @@ -438,7 +448,7 @@ export class TmdbService { public static getTvTopRated(options?: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/tv/top_rated' + url: '/api/v1/tmdb/tv/top_rated' }); } @@ -448,7 +458,7 @@ export class TmdbService { public static getTvDetails(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/tv/{series_id}' + url: '/api/v1/tmdb/tv/{series_id}' }); } @@ -458,7 +468,7 @@ export class TmdbService { public static getTvSeasonDetails(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/tv/{series_id}/season/{season_number}' + url: '/api/v1/tmdb/tv/{series_id}/season/{season_number}' }); } @@ -468,7 +478,7 @@ export class TmdbService { public static getTvEpisodeDetails(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/tv/{series_id}/season/{season_number}/episode/{episode_number}' + url: '/api/v1/tmdb/tv/{series_id}/season/{season_number}/episode/{episode_number}' }); } @@ -478,7 +488,7 @@ export class TmdbService { public static searchCollection(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/search/collection' + url: '/api/v1/tmdb/search/collection' }); } @@ -488,7 +498,7 @@ export class TmdbService { public static searchMovie(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/search/movie' + url: '/api/v1/tmdb/search/movie' }); } @@ -498,7 +508,7 @@ export class TmdbService { public static searchMulti(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/search/multi' + url: '/api/v1/tmdb/search/multi' }); } @@ -508,7 +518,7 @@ export class TmdbService { public static searchTv(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/search/tv' + url: '/api/v1/tmdb/search/tv' }); } @@ -518,7 +528,7 @@ export class TmdbService { public static getFromExternalId(options: Options) { return (options?.client ?? client).get({ ...options, - url: '/tmdb/external_id/{external_id}' + url: '/api/v1/tmdb/external_id/{external_id}' }); } diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts index be814c5..b51ff8a 100644 --- a/src/lib/client/types.gen.ts +++ b/src/lib/client/types.gen.ts @@ -17,6 +17,7 @@ export type AnnatarConfig = { export type AppModel = { version?: string; + api_key?: string; debug?: boolean; log?: boolean; force_refresh?: boolean; @@ -712,7 +713,7 @@ export type ZileanConfig = { export type RootResponse2 = (RootResponse); -export type RootError = (unknown); +export type RootError = unknown; export type HealthResponse = (MessageResponse); @@ -722,6 +723,10 @@ export type RdResponse = (RDUser); export type RdError = (unknown); +export type GenerateapikeyResponse = (MessageResponse); + +export type GenerateapikeyError = (unknown); + export type TorboxResponse = (unknown); export type TorboxError = (unknown); @@ -766,59 +771,11 @@ export type MountResponse = ({ export type MountError = (unknown); -export type OverseerrWebhookOverseerrPostResponse = ({ - [key: string]: unknown; -}); - -export type OverseerrWebhookOverseerrPostError = (unknown); - -export type GetSettingsSchemaResponse = ({ - [key: string]: unknown; -}); - -export type GetSettingsSchemaError = (unknown); - -export type LoadSettingsResponse = (MessageResponse); - -export type LoadSettingsError = (unknown); - -export type SaveSettingsResponse = (MessageResponse); - -export type SaveSettingsError = (unknown); - -export type GetAllSettingsResponse = (AppModel); - -export type GetAllSettingsError = (unknown); - -export type GetSettingsData = { - path: { - paths: string; - }; -}; - -export type GetSettingsResponse = ({ +export type OverseerrApiV1WebhookOverseerrPostResponse = ({ [key: string]: unknown; }); -export type GetSettingsError = (unknown | HTTPValidationError); - -export type SetAllSettingsData = { - body: { - [key: string]: unknown; - }; -}; - -export type SetAllSettingsResponse = (MessageResponse); - -export type SetAllSettingsError = (unknown | HTTPValidationError); - -export type SetSettingsData = { - body: Array; -}; - -export type SetSettingsResponse = (MessageResponse); - -export type SetSettingsError = (unknown | HTTPValidationError); +export type OverseerrApiV1WebhookOverseerrPostError = (unknown); export type GetStatesResponse = (StateResponse); @@ -921,7 +878,7 @@ export type SetTorrentRdMagnetResponse = (SetTorrentRDResponse); export type SetTorrentRdMagnetError = (unknown | HTTPValidationError); -export type SetTorrentRdItemsIdSetTorrentRdPostData = { +export type SetTorrentRdApiV1ItemsIdSetTorrentRdPostData = { path: { id: number; }; @@ -930,9 +887,9 @@ export type SetTorrentRdItemsIdSetTorrentRdPostData = { }; }; -export type SetTorrentRdItemsIdSetTorrentRdPostResponse = (SetTorrentRDResponse); +export type SetTorrentRdApiV1ItemsIdSetTorrentRdPostResponse = (SetTorrentRDResponse); -export type SetTorrentRdItemsIdSetTorrentRdPostError = (unknown | HTTPValidationError); +export type SetTorrentRdApiV1ItemsIdSetTorrentRdPostError = (unknown | HTTPValidationError); export type ScrapeData = { query: { @@ -956,6 +913,54 @@ export type GetRdTorrentsResponse = (Array); export type GetRdTorrentsError = (HTTPValidationError); +export type GetSettingsSchemaResponse = ({ + [key: string]: unknown; +}); + +export type GetSettingsSchemaError = (unknown); + +export type LoadSettingsResponse = (MessageResponse); + +export type LoadSettingsError = (unknown); + +export type SaveSettingsResponse = (MessageResponse); + +export type SaveSettingsError = (unknown); + +export type GetAllSettingsResponse = (AppModel); + +export type GetAllSettingsError = (unknown); + +export type GetSettingsData = { + path: { + paths: string; + }; +}; + +export type GetSettingsResponse = ({ + [key: string]: unknown; +}); + +export type GetSettingsError = (unknown | HTTPValidationError); + +export type SetAllSettingsData = { + body: { + [key: string]: unknown; + }; +}; + +export type SetAllSettingsResponse = (MessageResponse); + +export type SetAllSettingsError = (unknown | HTTPValidationError); + +export type SetSettingsData = { + body: Array; +}; + +export type SetSettingsResponse = (MessageResponse); + +export type SetSettingsError = (unknown | HTTPValidationError); + export type GetTrendingData = { path: { type: TrendingType; diff --git a/src/lib/serverConfig.ts b/src/lib/serverConfig.ts new file mode 100644 index 0000000..402687c --- /dev/null +++ b/src/lib/serverConfig.ts @@ -0,0 +1,22 @@ +import fs from 'fs/promises'; +import path from 'path'; + +const CONFIG_FILE = path.join(process.cwd(), 'server-config.json'); + +interface ServerConfig { + backendUrl: string; + apiKey: string; +} + +export async function getServerConfig(): Promise { + try { + const data = await fs.readFile(CONFIG_FILE, 'utf-8'); + return JSON.parse(data); + } catch { + return null; + } +} + +export async function setServerConfig(config: ServerConfig): Promise { + await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2)); +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d9aa88c..0a31a8e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -11,23 +11,23 @@ import { source } from 'sveltekit-sse'; import { toast } from 'svelte-sonner'; - function handleSSE(message: string) { - const data = JSON.parse(message); - - if (data.type === 'health') { - toast.info('Websocket is connected'); - } - - if (data.type === 'item_update') { - const item = JSON.parse(data.item); - toast.info(`${item.type} ${item.title} has been updated to ${item.state}`); - } - } - - const value = source('/api/sse').select('message'); - $: if ($value) { - handleSSE($value); - } + // function handleSSE(message: string) { + // const data = JSON.parse(message); + + // if (data.type === 'health') { + // toast.info('Websocket is connected'); + // } + + // if (data.type === 'item_update') { + // const item = JSON.parse(data.item); + // toast.info(`${item.type} ${item.title} has been updated to ${item.state}`); + // } + // } + + // const value = source('/api/sse').select('message'); + // $: if ($value) { + // handleSSE($value); + // } const showMenu: Writable = writable(false); diff --git a/src/routes/api/[...backend_proxy]/+server.ts b/src/routes/api/[...backend_proxy]/+server.ts index 18cee82..744a5d9 100644 --- a/src/routes/api/[...backend_proxy]/+server.ts +++ b/src/routes/api/[...backend_proxy]/+server.ts @@ -3,11 +3,15 @@ import type { RequestHandler } from '@sveltejs/kit'; export const GET: RequestHandler = async ({ locals, url }) => { const { pathname } = url; - const backendPathname = pathname.replace('/api', ''); - const backendUrl = new URL(backendPathname, locals.BACKEND_URL); + const backendUrl = new URL(pathname, locals.backendUrl); + const apiKey = locals.apiKey; try { - const response = await fetch(`${backendUrl.toString()}${url.search}`); + const response = await fetch(`${backendUrl.toString()}${url.search}`, { + headers: { + 'x-api-key': apiKey + } + }); return json(await response.json(), { status: response.status }); @@ -18,14 +22,15 @@ export const GET: RequestHandler = async ({ locals, url }) => { export const PUT: RequestHandler = async ({ locals, url, request }) => { const { pathname } = url; - const backendPathname = pathname.replace('/api', ''); - const backendUrl = new URL(backendPathname, locals.BACKEND_URL); + const backendUrl = new URL(pathname, locals.backendUrl); + const apiKey = locals.apiKey; try { const response = await fetch(`${backendUrl.toString()}${url.search}`, { method: 'PUT', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'x-api-key': apiKey }, body: await request.text() }); @@ -39,14 +44,15 @@ export const PUT: RequestHandler = async ({ locals, url, request }) => { export const POST: RequestHandler = async ({ locals, url, request }) => { const { pathname } = url; - const backendPathname = pathname.replace('/api', ''); - const backendUrl = new URL(backendPathname, locals.BACKEND_URL); + const backendUrl = new URL(pathname, locals.backendUrl); + const apiKey = locals.apiKey; try { const response = await fetch(`${backendUrl.toString()}${url.search}`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'x-api-key': apiKey }, body: await request.text() }); @@ -60,12 +66,15 @@ export const POST: RequestHandler = async ({ locals, url, request }) => { export const DELETE: RequestHandler = async ({ locals, url }) => { const { pathname } = url; - const backendPathname = pathname.replace('/api', ''); - const backendUrl = new URL(backendPathname, locals.BACKEND_URL); + const backendUrl = new URL(pathname, locals.backendUrl); + const apiKey = locals.apiKey; try { const response = await fetch(`${backendUrl.toString()}${url.search}`, { - method: 'DELETE' + method: 'DELETE', + headers: { + 'x-api-key': apiKey + } }); return json(await response.json(), { status: response.status diff --git a/src/routes/api/configure-client/+server.ts b/src/routes/api/configure-client/+server.ts new file mode 100644 index 0000000..c5fea83 --- /dev/null +++ b/src/routes/api/configure-client/+server.ts @@ -0,0 +1,69 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { client, DefaultService } from '$lib/client/services.gen'; +import { setServerConfig, getServerConfig } from '$lib/serverConfig'; + +export const POST: RequestHandler = async ({ request }) => { + const { backendUrl, apiKey } = await request.json(); + + if (!backendUrl || !apiKey) { + return json({ success: false, message: 'Missing backendUrl or apiKey' }, { status: 400 }); + } + + try { + // Validate the connection + const healthResponse = await fetch(`${backendUrl}/api/v1/health`, { + method: 'GET', + headers: { + 'x-api-key': apiKey + } + }); + + if (healthResponse.status !== 200) { + if (healthResponse.status === 401) { + return json({ success: false, message: 'Invalid API Key' }, { status: 401 }); + } + return json({ success: false, message: 'Unknown error' }, { status: healthResponse.status }); + } + + // Store the configuration on the server + await setServerConfig({ backendUrl, apiKey }); + + // Configure the client with new values + client.setConfig({ + baseUrl: backendUrl, + headers: { + 'x-api-key': apiKey + } + }); + + // Check services + const { data, error: apiError } = await DefaultService.services(); + if (apiError || !data) { + return json( + { success: false, message: 'API Error', requiresOnboarding: true }, + { status: 500 } + ); + } + + const toCheck = ['symlink', 'symlinklibrary']; + const allServicesTrue: boolean = toCheck.every((service) => data[service] === true); + + return json({ + success: true, + message: 'Client configured successfully', + requiresOnboarding: !allServicesTrue + }); + } catch { + return json({ success: false, message: 'Error connecting to the backend' }, { status: 500 }); + } +}; + +export const GET: RequestHandler = async () => { + const config = await getServerConfig(); + if (config) { + return json(config); + } else { + return json({ success: false, message: 'No configuration found' }, { status: 404 }); + } +}; diff --git a/src/routes/api/sse/+server.ts b/src/routes/api/sse/+server.ts deleted file mode 100644 index 5e95b61..0000000 --- a/src/routes/api/sse/+server.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { produce } from 'sveltekit-sse'; -import WebSocket from 'ws'; -import { env } from '$env/dynamic/private'; -const BACKEND_URL = env.BACKEND_URL || 'http://127.0.0.1:8080'; -import { building } from '$app/environment'; - -let websocket: WebSocket; -if (!building) { - websocket = new WebSocket(`${BACKEND_URL}/ws`, { - perMessageDeflate: false - }); -} - -export function POST() { - return produce(async function start({ emit }) { - websocket.on('message', (data) => { - const { error } = emit('message', data.toString('utf-8')); - if (error) { - console.error(error); - return; - } - }); - websocket.on('error', (error) => { - console.error(error); - }); - }); -} diff --git a/src/routes/connect/+page.svelte b/src/routes/connect/+page.svelte new file mode 100644 index 0000000..282ce59 --- /dev/null +++ b/src/routes/connect/+page.svelte @@ -0,0 +1,95 @@ + + +
+
+
+
+ + +
+
+ + +
+ {#if errorMessage} +

{errorMessage}

+ {/if} + +
+
+