started implementing radarr and sonarr
This commit is contained in:
parent
7b363964f7
commit
f198d98437
23 changed files with 45022 additions and 15 deletions
27
Caddyfile
Normal file
27
Caddyfile
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
http://localhost
|
||||||
|
|
||||||
|
route /sonarr/api/v3/* {
|
||||||
|
uri strip_prefix /sonarr
|
||||||
|
reverse_proxy sonarr_v3:4010
|
||||||
|
}
|
||||||
|
|
||||||
|
route /sonarr/api/v5/* {
|
||||||
|
uri strip_prefix /sonarr
|
||||||
|
reverse_proxy sonarr_v5:4010
|
||||||
|
}
|
||||||
|
|
||||||
|
route /radarr/* {
|
||||||
|
uri strip_prefix /radarr
|
||||||
|
reverse_proxy radarr:4010
|
||||||
|
}
|
||||||
|
|
||||||
|
# route /tmdb/* {
|
||||||
|
# uri strip_prefix /tmdb
|
||||||
|
# reverse_proxy tmdb:4010
|
||||||
|
# }
|
||||||
|
|
||||||
|
route /jellifin/* {
|
||||||
|
uri strip_prefix /jellifin
|
||||||
|
reverse_proxy jellifin:4010
|
||||||
|
}
|
|
@ -5,6 +5,12 @@
|
||||||
|
|
||||||
## APIS
|
## APIS
|
||||||
|
|
||||||
|
### Host mocked api's
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
### Generate openapi client
|
### Generate openapi client
|
||||||
|
|
||||||
- path to source yml or json
|
- path to source yml or json
|
||||||
|
@ -12,5 +18,5 @@
|
||||||
|
|
||||||
example
|
example
|
||||||
```bash
|
```bash
|
||||||
bunx openapi-typescript .\src\features\content\apis\api.yml -o .\src\features\content\apis\api.generated.ts
|
bunx openapi-typescript .\src\features\content\apis\api.json -o .\src\features\content\apis\api.generated.ts
|
||||||
```
|
```
|
54
docker-compose.mocks.yml
Normal file
54
docker-compose.mocks.yml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
proxy:
|
||||||
|
image: caddy
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||||
|
ports:
|
||||||
|
- '8080:80'
|
||||||
|
depends_on:
|
||||||
|
- sonarr_v3
|
||||||
|
- sonarr_v5
|
||||||
|
- radarr
|
||||||
|
- jellyfin
|
||||||
|
|
||||||
|
sonarr_v3:
|
||||||
|
image: stoplight/prism
|
||||||
|
volumes:
|
||||||
|
- './src/features/content/apis:/var/apis'
|
||||||
|
command: >-
|
||||||
|
mock
|
||||||
|
-h 0.0.0.0
|
||||||
|
-p 4010
|
||||||
|
/var/apis/sonarr.v3.json
|
||||||
|
|
||||||
|
sonarr_v5:
|
||||||
|
image: stoplight/prism
|
||||||
|
volumes:
|
||||||
|
- './src/features/content/apis:/var/apis'
|
||||||
|
command: >-
|
||||||
|
mock
|
||||||
|
-h 0.0.0.0
|
||||||
|
-p 4010
|
||||||
|
/var/apis/sonarr.v5.json
|
||||||
|
|
||||||
|
radarr:
|
||||||
|
image: stoplight/prism
|
||||||
|
volumes:
|
||||||
|
- './src/features/content/apis:/var/apis'
|
||||||
|
command: >-
|
||||||
|
mock
|
||||||
|
-h 0.0.0.0
|
||||||
|
-p 4010
|
||||||
|
/var/apis/radarr.json
|
||||||
|
|
||||||
|
jellyfin:
|
||||||
|
image: stoplight/prism
|
||||||
|
volumes:
|
||||||
|
- './src/features/content/apis:/var/apis'
|
||||||
|
command: >-
|
||||||
|
mock
|
||||||
|
-h 0.0.0.0
|
||||||
|
-p 4010
|
||||||
|
/var/apis/jellyfin.json
|
||||||
|
/var/apis/jellyfin.json
|
|
@ -1,8 +1,17 @@
|
||||||
|
version: '3'
|
||||||
services:
|
services:
|
||||||
jellyfin:
|
sonarr:
|
||||||
image: stoplight/prism
|
image: hotio/sonarr
|
||||||
ports:
|
container_name: sonarr
|
||||||
- '9003:4010'
|
|
||||||
volumes:
|
volumes:
|
||||||
- './src/features/content/apis:/var/apis'
|
- ./media:/media
|
||||||
command: 'mock -h 0.0.0.0 /var/apis/jellyfin.json'
|
ports:
|
||||||
|
- 8989:8989
|
||||||
|
|
||||||
|
radarr:
|
||||||
|
image: hotio/radarr
|
||||||
|
container_name: radarr
|
||||||
|
volumes:
|
||||||
|
- ./media:/media
|
||||||
|
ports:
|
||||||
|
- 7878:7878
|
9275
src/features/content/apis/radarr.generated.ts
Normal file
9275
src/features/content/apis/radarr.generated.ts
Normal file
File diff suppressed because it is too large
Load diff
12181
src/features/content/apis/radarr.json
Normal file
12181
src/features/content/apis/radarr.json
Normal file
File diff suppressed because it is too large
Load diff
49
src/features/content/apis/radarr.ts
Normal file
49
src/features/content/apis/radarr.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import type { paths } from "./radarr.generated";
|
||||||
|
import { query } from "@solidjs/router";
|
||||||
|
import createClient from "openapi-fetch";
|
||||||
|
|
||||||
|
const getBaseUrl = () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
return process.env.RADARR_BASE_URL;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
return createClient<paths>({
|
||||||
|
baseUrl: getBaseUrl(),
|
||||||
|
headers: {
|
||||||
|
"X-Api-Key": `${process.env.RADARR_API_KEY}`,
|
||||||
|
"Content-Type": 'application/json;',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const get = query(async () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
const { data, error } = await getClient().GET('/api/v3/movie');
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}, 'radarr.get');
|
||||||
|
|
||||||
|
export const addMovie = query(async (id: string) => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
const { data, error } = await getClient().POST('/api/v3/movie', {
|
||||||
|
body: {
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}, 'radarr.get');
|
||||||
|
|
||||||
|
export const listIds = query(async () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
const { data, error } = await getClient().GET('/api/v3/movie');
|
||||||
|
|
||||||
|
return Object.fromEntries(data?.map(({ id, tmdbId }) => ([ `m${tmdbId}`, { radarr: id } ] as const)) ?? []);
|
||||||
|
}, 'radarr.listIds');
|
57
src/features/content/apis/sonarr.ts
Normal file
57
src/features/content/apis/sonarr.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import type { paths as v3Paths } from "./sonarr.v3.generated";
|
||||||
|
import type { paths as v5Paths } from "./sonarr.v5.generated";
|
||||||
|
import { query } from "@solidjs/router";
|
||||||
|
import createClient from "openapi-fetch";
|
||||||
|
|
||||||
|
const getBaseUrl = () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
return process.env.SONARR_BASE_URL;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
return createClient<v3Paths&v5Paths>({
|
||||||
|
baseUrl: getBaseUrl(),
|
||||||
|
headers: {
|
||||||
|
"X-Api-Key": `${process.env.SONARR_API_KEY}`,
|
||||||
|
"Content-Type": 'application/json;',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST = query(async () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
const { data } = await getClient().GET('/api/v3/series', {
|
||||||
|
params: {
|
||||||
|
query: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}, 'sonarr.TEST');
|
||||||
|
|
||||||
|
export const getByTmdbId = query(async (id: string) => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
const { data } = await getClient().GET('/api/v3/series/lookup', {
|
||||||
|
params: {
|
||||||
|
query: {
|
||||||
|
term: `tmdb:${id}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data?.[0];
|
||||||
|
}, 'sonarr.getByTmdbId');
|
||||||
|
|
||||||
|
export const listIds = query(async () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
const { data, error } = await getClient().GET('/api/v3/series');
|
||||||
|
|
||||||
|
return Object.fromEntries(data?.map(({ id, tmdbId }) => ([ `s${tmdbId}`, { sonarr: id } ] as const)) ?? []);
|
||||||
|
}, 'sonarr.listIds');
|
9216
src/features/content/apis/sonarr.v3.generated.ts
Normal file
9216
src/features/content/apis/sonarr.v3.generated.ts
Normal file
File diff suppressed because it is too large
Load diff
11887
src/features/content/apis/sonarr.v3.json
Normal file
11887
src/features/content/apis/sonarr.v3.json
Normal file
File diff suppressed because it is too large
Load diff
902
src/features/content/apis/sonarr.v5.generated.ts
Normal file
902
src/features/content/apis/sonarr.v5.generated.ts
Normal file
|
@ -0,0 +1,902 @@
|
||||||
|
/**
|
||||||
|
* This file was auto-generated by openapi-typescript.
|
||||||
|
* Do not make direct changes to the file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface paths {
|
||||||
|
"/api": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/login": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
returnUrl?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: {
|
||||||
|
content: {
|
||||||
|
"multipart/form-data": {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
rememberMe?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/logout": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/v5/log": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
sortKey?: string;
|
||||||
|
sortDirection?: components["schemas"]["SortDirection"];
|
||||||
|
level?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["LogResourcePagingResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/ping": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["PingResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["PingResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/v5/series": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
tvdbId?: number;
|
||||||
|
includeSeasonImages?: boolean;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SeriesResource"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SeriesResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SeriesResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/v5/series/{id}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
includeSeasonImages?: boolean;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SeriesResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
moveFiles?: boolean;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SeriesResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SeriesResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
post?: never;
|
||||||
|
delete: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
deleteFiles?: boolean;
|
||||||
|
addImportListExclusion?: boolean;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/v5/series/{id}/folder": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/v5/series/lookup": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
term?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"text/plain": components["schemas"]["SeriesResource"][];
|
||||||
|
"application/json": components["schemas"]["SeriesResource"][];
|
||||||
|
"text/json": components["schemas"]["SeriesResource"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/content/{path}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/{path}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/v5/update": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["UpdateResource"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/v5/settings/update": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"text/plain": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
"application/json": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
"text/json": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
"text/json": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
"application/*+json": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"text/plain": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
"application/json": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
"text/json": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/v5/settings/update/{id}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["UpdateSettingsResource"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export type webhooks = Record<string, never>;
|
||||||
|
export interface components {
|
||||||
|
schemas: {
|
||||||
|
AddSeriesOptions: {
|
||||||
|
ignoreEpisodesWithFiles?: boolean;
|
||||||
|
ignoreEpisodesWithoutFiles?: boolean;
|
||||||
|
monitor?: components["schemas"]["MonitorTypes"];
|
||||||
|
searchForMissingEpisodes?: boolean;
|
||||||
|
searchForCutoffUnmetEpisodes?: boolean;
|
||||||
|
};
|
||||||
|
AlternateTitleResource: {
|
||||||
|
title?: string | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
seasonNumber?: number | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
sceneSeasonNumber?: number | null;
|
||||||
|
sceneOrigin?: string | null;
|
||||||
|
comment?: string | null;
|
||||||
|
};
|
||||||
|
Language: {
|
||||||
|
/** Format: int32 */
|
||||||
|
id?: number;
|
||||||
|
name?: string | null;
|
||||||
|
};
|
||||||
|
LogResource: {
|
||||||
|
/** Format: int32 */
|
||||||
|
id?: number;
|
||||||
|
/** Format: date-time */
|
||||||
|
time?: string;
|
||||||
|
exception?: string | null;
|
||||||
|
exceptionType?: string | null;
|
||||||
|
level: string | null;
|
||||||
|
logger: string | null;
|
||||||
|
message: string | null;
|
||||||
|
};
|
||||||
|
LogResourcePagingResource: {
|
||||||
|
/** Format: int32 */
|
||||||
|
page?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
pageSize?: number;
|
||||||
|
sortKey?: string | null;
|
||||||
|
sortDirection?: components["schemas"]["SortDirection"];
|
||||||
|
/** Format: int32 */
|
||||||
|
totalRecords?: number;
|
||||||
|
records?: components["schemas"]["LogResource"][] | null;
|
||||||
|
};
|
||||||
|
MediaCover: {
|
||||||
|
coverType?: components["schemas"]["MediaCoverTypes"];
|
||||||
|
url?: string | null;
|
||||||
|
remoteUrl?: string | null;
|
||||||
|
};
|
||||||
|
/** @enum {string} */
|
||||||
|
MediaCoverTypes: "unknown" | "poster" | "banner" | "fanart" | "screenshot" | "headshot" | "clearlogo";
|
||||||
|
/** @enum {string} */
|
||||||
|
MonitorTypes: "unknown" | "all" | "future" | "missing" | "existing" | "firstSeason" | "lastSeason" | "latestSeason" | "pilot" | "recent" | "monitorSpecials" | "unmonitorSpecials" | "none" | "skip";
|
||||||
|
/** @enum {string} */
|
||||||
|
NewItemMonitorTypes: "all" | "none";
|
||||||
|
PingResource: {
|
||||||
|
status?: string | null;
|
||||||
|
};
|
||||||
|
Ratings: {
|
||||||
|
/** Format: int32 */
|
||||||
|
votes?: number;
|
||||||
|
/** Format: double */
|
||||||
|
value?: number;
|
||||||
|
};
|
||||||
|
SeasonResource: {
|
||||||
|
/** Format: int32 */
|
||||||
|
seasonNumber?: number;
|
||||||
|
monitored?: boolean;
|
||||||
|
statistics?: components["schemas"]["SeasonStatisticsResource"];
|
||||||
|
images?: components["schemas"]["MediaCover"][] | null;
|
||||||
|
};
|
||||||
|
SeasonStatisticsResource: {
|
||||||
|
/** Format: date-time */
|
||||||
|
nextAiring?: string | null;
|
||||||
|
/** Format: date-time */
|
||||||
|
previousAiring?: string | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
episodeFileCount?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
episodeCount?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
totalEpisodeCount?: number;
|
||||||
|
/** Format: int64 */
|
||||||
|
sizeOnDisk?: number;
|
||||||
|
releaseGroups?: string[] | null;
|
||||||
|
/** Format: double */
|
||||||
|
readonly percentOfEpisodes?: number;
|
||||||
|
};
|
||||||
|
SeriesResource: {
|
||||||
|
/** Format: int32 */
|
||||||
|
id?: number;
|
||||||
|
title?: string | null;
|
||||||
|
alternateTitles?: components["schemas"]["AlternateTitleResource"][] | null;
|
||||||
|
sortTitle?: string | null;
|
||||||
|
status?: components["schemas"]["SeriesStatusType"];
|
||||||
|
readonly ended?: boolean;
|
||||||
|
profileName?: string | null;
|
||||||
|
overview?: string | null;
|
||||||
|
/** Format: date-time */
|
||||||
|
nextAiring?: string | null;
|
||||||
|
/** Format: date-time */
|
||||||
|
previousAiring?: string | null;
|
||||||
|
network?: string | null;
|
||||||
|
airTime?: string | null;
|
||||||
|
images?: components["schemas"]["MediaCover"][] | null;
|
||||||
|
originalLanguage?: components["schemas"]["Language"];
|
||||||
|
remotePoster?: string | null;
|
||||||
|
seasons?: components["schemas"]["SeasonResource"][] | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
year?: number;
|
||||||
|
path?: string | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
qualityProfileId?: number;
|
||||||
|
seasonFolder?: boolean;
|
||||||
|
monitored?: boolean;
|
||||||
|
monitorNewItems?: components["schemas"]["NewItemMonitorTypes"];
|
||||||
|
useSceneNumbering?: boolean;
|
||||||
|
/** Format: int32 */
|
||||||
|
runtime?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
tvdbId?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
tvRageId?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
tvMazeId?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
tmdbId?: number;
|
||||||
|
/** Format: date-time */
|
||||||
|
firstAired?: string | null;
|
||||||
|
/** Format: date-time */
|
||||||
|
lastAired?: string | null;
|
||||||
|
seriesType?: components["schemas"]["SeriesTypes"];
|
||||||
|
cleanTitle?: string | null;
|
||||||
|
imdbId?: string | null;
|
||||||
|
titleSlug?: string | null;
|
||||||
|
rootFolderPath?: string | null;
|
||||||
|
folder?: string | null;
|
||||||
|
certification?: string | null;
|
||||||
|
genres?: string[] | null;
|
||||||
|
tags?: number[] | null;
|
||||||
|
/** Format: date-time */
|
||||||
|
added?: string;
|
||||||
|
addOptions?: components["schemas"]["AddSeriesOptions"];
|
||||||
|
ratings?: components["schemas"]["Ratings"];
|
||||||
|
statistics?: components["schemas"]["SeriesStatisticsResource"];
|
||||||
|
episodesChanged?: boolean | null;
|
||||||
|
};
|
||||||
|
SeriesStatisticsResource: {
|
||||||
|
/** Format: int32 */
|
||||||
|
seasonCount?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
episodeFileCount?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
episodeCount?: number;
|
||||||
|
/** Format: int32 */
|
||||||
|
totalEpisodeCount?: number;
|
||||||
|
/** Format: int64 */
|
||||||
|
sizeOnDisk?: number;
|
||||||
|
releaseGroups?: string[] | null;
|
||||||
|
/** Format: double */
|
||||||
|
readonly percentOfEpisodes?: number;
|
||||||
|
};
|
||||||
|
/** @enum {string} */
|
||||||
|
SeriesStatusType: "continuing" | "ended" | "upcoming" | "deleted";
|
||||||
|
/** @enum {string} */
|
||||||
|
SeriesTypes: "standard" | "daily" | "anime";
|
||||||
|
/** @enum {string} */
|
||||||
|
SortDirection: "default" | "ascending" | "descending";
|
||||||
|
UpdateChanges: {
|
||||||
|
new?: string[] | null;
|
||||||
|
fixed?: string[] | null;
|
||||||
|
};
|
||||||
|
/** @enum {string} */
|
||||||
|
UpdateMechanism: "builtIn" | "script" | "external" | "apt" | "docker";
|
||||||
|
UpdateResource: {
|
||||||
|
/** Format: int32 */
|
||||||
|
id?: number;
|
||||||
|
version: string | null;
|
||||||
|
branch: string | null;
|
||||||
|
/** Format: date-time */
|
||||||
|
releaseDate?: string;
|
||||||
|
fileName: string | null;
|
||||||
|
url: string | null;
|
||||||
|
installed?: boolean;
|
||||||
|
/** Format: date-time */
|
||||||
|
installedOn?: string | null;
|
||||||
|
installable?: boolean;
|
||||||
|
latest?: boolean;
|
||||||
|
changes: components["schemas"]["UpdateChanges"];
|
||||||
|
hash: string | null;
|
||||||
|
};
|
||||||
|
UpdateSettingsResource: {
|
||||||
|
/** Format: int32 */
|
||||||
|
id?: number;
|
||||||
|
branch?: string | null;
|
||||||
|
updateAutomatically?: boolean;
|
||||||
|
updateMechanism?: components["schemas"]["UpdateMechanism"];
|
||||||
|
updateScriptPath?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: never;
|
||||||
|
parameters: never;
|
||||||
|
requestBodies: never;
|
||||||
|
headers: never;
|
||||||
|
pathItems: never;
|
||||||
|
}
|
||||||
|
export type $defs = Record<string, never>;
|
||||||
|
export type operations = Record<string, never>;
|
1260
src/features/content/apis/sonarr.v5.json
Normal file
1260
src/features/content/apis/sonarr.v5.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
||||||
import createClient from "openapi-fetch";
|
import createClient from "openapi-fetch";
|
||||||
import { query } from "@solidjs/router";
|
import { query } from "@solidjs/router";
|
||||||
import { Entry, SearchResult } from "../types";
|
import { Entry, SearchResult } from "../types";
|
||||||
import { paths as pathsV3, operations } from "./tmdb.generated";
|
import { paths as pathsV3 } from "./tmdb.v3.generated";
|
||||||
import { paths as pathsV4 } from "./tmdb.not.generated";
|
import { paths as pathsV4 } from "./tmdb.not.generated";
|
||||||
|
|
||||||
interface TMDBItem {
|
interface TMDBItem {
|
||||||
|
@ -58,8 +58,6 @@ export const getEntry = query(
|
||||||
tv: { series_id: Number.parseInt(id.slice(1)) },
|
tv: { series_id: Number.parseInt(id.slice(1)) },
|
||||||
} as const)[mediaType];
|
} as const)[mediaType];
|
||||||
|
|
||||||
console.log(`going to fetch from '${endpoint}' with id '${id}'`)
|
|
||||||
|
|
||||||
const { data } = await clientV3.GET(endpoint, {
|
const { data } = await clientV3.GET(endpoint, {
|
||||||
params: {
|
params: {
|
||||||
path: params,
|
path: params,
|
||||||
|
|
|
@ -9,16 +9,41 @@ import {
|
||||||
getEntry as getTmdbEntry,
|
getEntry as getTmdbEntry,
|
||||||
searchMulti,
|
searchMulti,
|
||||||
} from "./apis/tmdb";
|
} from "./apis/tmdb";
|
||||||
|
import { listIds as listSerieIds } from "./apis/sonarr";
|
||||||
|
import { listIds as listMovieIds } from "./apis/radarr";
|
||||||
|
import { merge } from "~/utilities";
|
||||||
|
|
||||||
const jellyfinUserId = "a9c51af84bf54578a99ab4dd0ebf0763";
|
const jellyfinUserId = "a9c51af84bf54578a99ab4dd0ebf0763";
|
||||||
|
|
||||||
const lookupTable = query(async () => listItemIds(), 'content.lookupTable');
|
const lookupTable = query(async () => {
|
||||||
|
'use server';
|
||||||
|
const [items, sonarr, radarr] = await Promise.all([
|
||||||
|
listItemIds(), listSerieIds(), listMovieIds() ]);
|
||||||
|
|
||||||
|
return merge(items, sonarr, radarr);
|
||||||
|
}, 'content.lookupTable');
|
||||||
|
|
||||||
export const getHighlights = () => getContinueWatching(jellyfinUserId);
|
export const getHighlights = () => getContinueWatching(jellyfinUserId);
|
||||||
export const getStream = query(async (id: string, range: string) => {
|
export const getStream = query(async (id: string, range: string) => {
|
||||||
const table = await lookupTable();
|
const table = await lookupTable();
|
||||||
|
const ids = table[id];
|
||||||
|
|
||||||
return getItemStream(table[id].jellyfin, range);
|
if (ids.jellyfin) {
|
||||||
|
return getItemStream(ids.jellyfin, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - If the lookup table has no entry
|
||||||
|
// than this means that we do not have the requested entry at all,
|
||||||
|
// neither in trackers nor in the media server
|
||||||
|
//
|
||||||
|
// - If the lookup table contains a jellyfin id,
|
||||||
|
// than we have the content and can stream straight away
|
||||||
|
//
|
||||||
|
// - If we have the radarr or sonarr id,
|
||||||
|
// than we are tracking the entry,
|
||||||
|
// but it is not available for use yet
|
||||||
|
console.log(ids);
|
||||||
|
|
||||||
}, 'content.stream');
|
}, 'content.stream');
|
||||||
|
|
||||||
export const listCategories = query(async (): Promise<Category[]> => {
|
export const listCategories = query(async (): Promise<Category[]> => {
|
||||||
|
@ -35,7 +60,7 @@ export const listCategories = query(async (): Promise<Category[]> => {
|
||||||
|
|
||||||
export const getEntryFromSlug = query(
|
export const getEntryFromSlug = query(
|
||||||
async (slug: string): Promise<Entry | undefined> => {
|
async (slug: string): Promise<Entry | undefined> => {
|
||||||
const { id } = slug.match(/^.+-(?<id>\w+)$/)?.groups ?? {};
|
const id = slug.match(/\w+$/)![0];
|
||||||
|
|
||||||
return getTmdbEntry(id);
|
return getTmdbEntry(id);
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
background: linear-gradient(to bottom, black, transparent) top left / 100% 20% no-repeat;
|
background: linear-gradient(to bottom, black, transparent) top left / 100% 20% no-repeat;
|
||||||
|
|
||||||
& > header {
|
& > header {
|
||||||
|
z-index: 1;
|
||||||
display: block grid;
|
display: block grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
|
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > section {
|
& > section {
|
||||||
|
z-index: 2;
|
||||||
display: block grid;
|
display: block grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
|
|
||||||
|
@ -59,6 +61,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > footer {
|
& > footer {
|
||||||
|
z-index: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block grid;
|
display: block grid;
|
||||||
grid: auto auto / auto auto auto;
|
grid: auto auto / auto auto auto;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
RouteDefinition,
|
RouteDefinition,
|
||||||
useParams,
|
useParams,
|
||||||
} from "@solidjs/router";
|
} from "@solidjs/router";
|
||||||
import { Show } from "solid-js";
|
import { createEffect, Show } from "solid-js";
|
||||||
import { Details } from "~/components/details";
|
import { Details } from "~/components/details";
|
||||||
import {
|
import {
|
||||||
createSlug,
|
createSlug,
|
||||||
|
|
23
src/routes/experimental.tsx
Normal file
23
src/routes/experimental.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { A } from "@solidjs/router";
|
||||||
|
import { ParentProps } from "solid-js";
|
||||||
|
|
||||||
|
export default function Experimental(props: ParentProps) {
|
||||||
|
return (
|
||||||
|
<div style={{ overflow: "auto" }}>
|
||||||
|
<nav
|
||||||
|
style={{
|
||||||
|
position: "sticky",
|
||||||
|
"inset-block-start": 0,
|
||||||
|
display: "flex",
|
||||||
|
gap: "var(--size-2)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<A href="/">Home</A>
|
||||||
|
<A href="/experimental/sonarr">Sonarr</A>
|
||||||
|
<A href="/experimental/radarr">Radarr</A>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>{props.children}</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
3
src/routes/experimental/[...404].tsx
Normal file
3
src/routes/experimental/[...404].tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function NotFound() {
|
||||||
|
return <>NOT FOUND</>;
|
||||||
|
}
|
3
src/routes/experimental/index.tsx
Normal file
3
src/routes/experimental/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function Index() {
|
||||||
|
return <></>;
|
||||||
|
}
|
13
src/routes/experimental/sonarr.tsx
Normal file
13
src/routes/experimental/sonarr.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { createAsync } from "@solidjs/router";
|
||||||
|
import { createEffect } from "solid-js";
|
||||||
|
import { TEST } from "~/features/content/apis/sonarr";
|
||||||
|
|
||||||
|
export default function Sonarr() {
|
||||||
|
const result = createAsync(() => TEST());
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
console.log("the merged lookup table", result());
|
||||||
|
});
|
||||||
|
|
||||||
|
return <pre>{JSON.stringify(result(), null, 2)}</pre>;
|
||||||
|
}
|
|
@ -44,3 +44,19 @@ export const hash = (
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const merge = (...objects: Record<string, any>[]): Record<string, any> => {
|
||||||
|
if (objects.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = objects[0];
|
||||||
|
|
||||||
|
for (const key of new Set(objects.map(o => Object.keys(o)).flat())) {
|
||||||
|
const values = objects.filter(o => Object.hasOwn(o, key)).map(o => o[key]);
|
||||||
|
|
||||||
|
target[key] = values.every(v => v && typeof v === 'object' && !Array.isArray(v)) ? merge(...values) : values.at(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue