From 3c35b8925092e2ecf406f25a3cd502b70a0cf055 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 17 Jun 2025 16:13:40 +0200 Subject: [PATCH] worked on various things --- app.config.ts | 11 +-- auth.sqlite | Bin 45056 -> 45056 bytes .../2025-06-17T07-59-07.464Z.sql | 7 ++ flake.nix | 23 ++++++ src/auth.client.ts | 6 ++ src/{auth.ts => auth.server.ts} | 17 +--- src/features/content/apis/jellyfin.ts | 6 +- src/features/content/service.ts | 77 ++++++++++-------- src/features/player/context.tsx | 40 ++++----- src/features/player/controls/seekBar.tsx | 11 ++- src/features/player/player.tsx | 11 ++- src/features/shell/top.tsx | 2 +- src/features/theme/context.ts | 4 +- src/routes/(shell).tsx | 47 +++-------- src/routes/(shell)/play/[slug].tsx | 4 +- src/routes/api/auth/[...action].ts | 2 +- src/routes/sitemap.xml.ts | 12 ++- tsconfig.json | 2 +- 18 files changed, 156 insertions(+), 126 deletions(-) create mode 100644 better-auth_migrations/2025-06-17T07-59-07.464Z.sql create mode 100644 flake.nix create mode 100644 src/auth.client.ts rename src/{auth.ts => auth.server.ts} (75%) diff --git a/app.config.ts b/app.config.ts index 2efc67a..8a04eb4 100644 --- a/app.config.ts +++ b/app.config.ts @@ -1,14 +1,15 @@ import { defineConfig } from "@solidjs/start/config"; import solidSvg from "vite-plugin-solid-svg"; import devtools from "solid-devtools/vite"; +import { build, fileURLToPath } from "bun"; export default defineConfig({ vite: { plugins: [ - devtools({ - autoname: true, - }), - solidSvg(), + // devtools({ + // autoname: true, + // }), + // solidSvg(), ], }, solid: { @@ -19,7 +20,7 @@ export default defineConfig({ server: { preset: "bun", prerender: { - routes: ["/sitemaps.xml"], + routes: ["/sitemap.xml"], }, }, }); diff --git a/auth.sqlite b/auth.sqlite index ce8592604fe1b6079d57bc040eafa9cd8863304b..e9519d131932230531eeac3be57508c0a1869cf1 100644 GIT binary patch delta 2409 zcmZp8z|`=7X@WE(-$WT_MLq_-EBsmY5Z!n)z8Ir}$RpX>n>%a(-S(YF>$6a(=FUslKVXg_)&+ zsePHPiHnhek*ThMnXaLEh=GNbfuWV5sh+8!X%sGTu%%#vi-CcGk^c?@|DDZ(0+;yp zl$m`w33&jb=QRWW>&=1!5BcRqnDsfqh9P@{7i2KsIR?ISn*{}q@YOfzGjjRb`r6v^ zHnIDbml#?2hDQ~cS$c+Nn3N|*qq^n_4d0JvbT18n#aGFO(TDfO_ma9d6rfC|Qe-ZkO4C;{s2qEE{UzM4Ylc;a1 zXP}@Fo|%`DUtX->7ouQjpl4vM5T2Q5W@4>SVP>MK;8;+QlNz3y!YKPlbM~W;GUYCov*3joKcjYo2qYUY_4Yjf(pTjX^BOd`gTw~t|{pVN#X(; zHF5|A9VBv4^$-d+a9nZEW8j{*Sx{gKcYQ}ZqkwOytZ!p5Lyx2_Lm`8$ZD>$_aEWt) zce=AjvUx~JsUw3Exg`2T7DSeL zIYkD91{g=CSfo~ZWhA*LXZmM)1&3C78v12=7JKHF6ht|Dn$>$|1sX+$WaLI=8dfI5 zB=dqylbt=yJhKZT!koP<^}r@XcxL+N6lW%Tq-1+$mS-j!d1ZL!7!-Tvx*0?mWn?59 zrGsoR^bPfjOvSR+gy$SYwc zAaUQ2WJ6yUS3}>b&~m@Z^323=Hv`YC{0hIS&~o39KvVw^M`IsnuYzP_UywudQ#^vo zlk2PU%Y2O73{%|GOB0Q~3=_jm3zBk^OQRw}Gku)B3RB#(L5ef|+&!!OvOFt&gAFsI zvXaYvLISHILp)0&v;1;=D-DbzLjsNc!Xpj*LedRKHHLIO>rLLy82Ln4jqqk;`{e8XKUB13#D{j$=He5-sj(*pIpy*&(Ee3E=z zT_RE=j0^p|oRSL){Y?!Fy>*?veX5L0(!vviJ<9Tfq9V+5;|mQ+!Xo_hQ3ku2u zJWWdTOj1MQ4FkgpjEb{@O3gjZoeRjlRQgwi&Fy3QhkzK%ac3;%}xCs%{&VHyaFwg94)-6Dl;mw zJyUc;`~!15jDx%bveUf{BmIqxBlDe03XI(|bTggID~p{yoV)|GD&71dbTcB%Ov8N4 zavh_5%{;?&BTY?{ik&SCjhsyki*y5Fr7fuTu(UEUt+z5T(=#+Ph{{h(%gM}3jZaKY zPAx9h$uCIF%S_Qp%}vbA(J9DGE-5Wa)hQ^-Ps_|n)k!bPFD)oWHVR%58R4jij7*~t zr4<7LY?cu?4ylOafp6VrL4hTF^-cba9KOEI+_tv1Ib|u4$riruA!a^? zg@I|gg%(bE<*vq+UQVg*rEbPS!6v3rX(d$#X?~%F$?<6w#x5R)zD|h|A^A?}g>I=v z)k?{UIXOv*$=RVnK1x)l)Ir*kO84<>*iJocZNu~v9 z^`227A+8zOi3UDtUg0i&8S%lEhORj&k@>p1x)GikB~HmkRk=Cd0p5;fE;;6IB~IS% zB^k!4h8E_LW{GaP6_q7ZN>+yEai#`_rUoYF<`#ygwWv{HYGP$ + ({ id, name, email, emailVerified, image, username: preferred_username }), }, ], }), ], }); - -export const { signIn, signOut, useSession, ...client } = createAuthClient({ - plugins: [genericOAuthClient()], -}); diff --git a/src/features/content/apis/jellyfin.ts b/src/features/content/apis/jellyfin.ts index 7233b96..a307660 100644 --- a/src/features/content/apis/jellyfin.ts +++ b/src/features/content/apis/jellyfin.ts @@ -219,7 +219,7 @@ export const getItemStream = query( itemId, }, query: { - startTimeTicks: userData?.playbackPositionTicks, + // startTimeTicks: userData?.playbackPositionTicks, }, }, parseAs: 'stream', @@ -392,11 +392,11 @@ const toEntry = (item: components['schemas']['BaseItemDto']): Entry => { id: `${type}${item.ProviderIds!["Tmdb"]!}`, title: item.Name!, overview: item.Overview!, - thumbnail: new URL(`/Items/${item.Id}/Images/Primary`, getBaseUrl()), //await getItemImage(data.Id!, 'Primary'), + thumbnail: new URL(`/Items/${item.Id}/Images/Primary`, getBaseUrl()), image: new URL(`/Items/${item.Id}/Images/Backdrop`, getBaseUrl()), providers: { jellyfin: item.Id - }, + }, trailer: item.RemoteTrailers?.at(-1)?.Url ?? undefined, }; }; \ No newline at end of file diff --git a/src/features/content/service.ts b/src/features/content/service.ts index bd8e929..9f29678 100644 --- a/src/features/content/service.ts +++ b/src/features/content/service.ts @@ -1,5 +1,3 @@ -"use server"; - import type { Category, Entry } from "./types"; import { query, revalidate } from "@solidjs/router"; import { listItemIds, getContinueWatching, getItemStream, getRandomItems, getItemUserData } from "./apis/jellyfin"; @@ -26,44 +24,47 @@ const lookupTable = query(async () => { export const getHighlights = () => getContinueWatching(jellyfinUserId); export const getStream = query(async (id: string, range: string) => { - const table = await lookupTable(); - const ids = table[id]; - const manager = id[0] === 'm' ? 'radarr' : 'sonarr' + 'use server'; - if (ids?.jellyfin) { - return getItemStream(jellyfinUserId, ids.jellyfin, range); - } + const table = await lookupTable(); + const ids = table[id]; + const manager = id[0] === 'm' ? 'radarr' : 'sonarr' - if (ids?.radarr) { - console.log(`radarr has the entry '${ids.radarr}', but jellyfin does not (yet) have the file`); - console.log(await TEST(ids.radarr)) - return; - } + if (ids?.jellyfin) { + return getItemStream(jellyfinUserId, ids.jellyfin, range); + } - if (ids?.sonarr) { - console.log('sonarr has the entry, but jellyfin does not (yet) have the file'); - return; - } + if (ids?.radarr) { + console.log(`radarr has the entry '${ids.radarr}', but jellyfin does not (yet) have the file`); + console.log(await TEST(ids.radarr)) + return; + } - // - 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(`request the item '${id}' at ${manager}`); + if (ids?.sonarr) { + console.log('sonarr has the entry, but jellyfin does not (yet) have the file'); + return; + } - const res = await ((manager === 'radarr' ? addMovie : addSeries)(id.slice(1))); + // - 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(`request the item '${id}' at ${manager}`); - revalidate(lookupTable.keyFor()) - + const res = await ((manager === 'radarr' ? addMovie : addSeries)(id.slice(1))); + + revalidate(lookupTable.keyFor()); }, 'content.stream'); export const listCategories = query(async (): Promise => { + 'use server'; + return [ // { label: "Continue", entries: await getContinueWatching(jellyfinUserId) }, { @@ -76,21 +77,27 @@ export const listCategories = query(async (): Promise => { }, "content.categories.list"); export const getEntryFromSlug = query( - async (slug: string): Promise => getEntry(slug.match(/\w+$/)![0]), + async (slug: string): Promise => { + 'use server'; + + return getEntry(slug.match(/\w+$/)![0]); + }, "content.getFromSlug", ); export const getEntry = query( async (id: Entry["id"]): Promise => { + 'use server'; + const [entry, userData] = await Promise.all([ getTmdbEntry(id), getEntryUserData(id) ] as const); - if (entry && userData) { + if (entry !== undefined && userData !== undefined) { entry['offset'] = ticksToSeconds(userData.playbackPositionTicks ?? 0); } - + return entry; }, "content.get", @@ -98,6 +105,8 @@ export const getEntry = query( export const getEntryUserData = query( async (id: string): ReturnType => { + 'use server'; + const table = await lookupTable(); const { jellyfin } = table[id] ?? {}; diff --git a/src/features/player/context.tsx b/src/features/player/context.tsx index deaee6e..6bb65a3 100644 --- a/src/features/player/context.tsx +++ b/src/features/player/context.tsx @@ -5,7 +5,10 @@ import { } from "@solid-primitives/context"; import { Accessor, createEffect, on, onMount, Setter } from "solid-js"; import { createStore } from "solid-js/store"; -import { createEventListenerMap } from "@solid-primitives/event-listener"; +import { + createEventListener, + createEventListenerMap, +} from "@solid-primitives/event-listener"; import { createFullscreen } from "@solid-primitives/fullscreen"; type State = "playing" | "paused"; @@ -93,6 +96,7 @@ export const [VideoProvider, useVideo] = createContextProvider< currentTime: () => store.currentTime, setTime(time) { + console.log("time is set via api!", time); video!.currentTime = time; }, @@ -124,8 +128,6 @@ export const [VideoProvider, useVideo] = createContextProvider< }, }; - console.log(props.root, props.video); - if (isServer || video === undefined) { return api; } @@ -147,15 +149,6 @@ export const [VideoProvider, useVideo] = createContextProvider< video.volume = store.volume.value; }); - createEffect(() => { - console.log(store.currentTime, props.offset); - }); - - onMount(() => { - setStore("duration", video.duration); - setStore("currentTime", video.currentTime); - }); - createEventListenerMap(video, { play(e) { setStore("state", "playing"); @@ -167,6 +160,8 @@ export const [VideoProvider, useVideo] = createContextProvider< setStore("duration", video.duration); }, timeupdate(e) { + console.log("time update", video.currentTime, e); + setStore("currentTime", video.currentTime); }, volumeChange() { @@ -175,14 +170,15 @@ export const [VideoProvider, useVideo] = createContextProvider< progress(e) { const timeRanges = video.buffered; - setStore( - "buffered", - timeRanges.length > 0 ? timeRanges.end(timeRanges.length - 1) : 0 - ); - }, - canplay() { - console.log("can play!"); - // setStore("loading", false); + if (timeRanges.length === 0) { + return; + } + + const range = timeRanges.end(timeRanges.length - 1); + + console.log(range); + + setStore("buffered", range); }, canplaythrough() { console.log("can play through!"); @@ -191,6 +187,10 @@ export const [VideoProvider, useVideo] = createContextProvider< waiting() { setStore("loading", true); }, + loadedmetadata() { + console.log("metadata loaded"); + video.currentTime = props.offset ?? 0; + }, }); return api; diff --git a/src/features/player/controls/seekBar.tsx b/src/features/player/controls/seekBar.tsx index bc83143..a78ce2d 100644 --- a/src/features/player/controls/seekBar.tsx +++ b/src/features/player/controls/seekBar.tsx @@ -1,4 +1,4 @@ -import { Component } from "solid-js"; +import { Component, createEffect, on } from "solid-js"; import { useVideo } from "../context"; import css from "./seekBar.module.css"; @@ -7,6 +7,15 @@ interface SeekBarProps {} export const SeekBar: Component = () => { const video = useVideo(); + createEffect( + on( + () => [video.duration(), video.buffered(), video.currentTime()] as const, + ([duration, buffered, currentTime]) => { + console.log({ duration, buffered, currentTime }); + } + ) + ); + return (
{formatTime(video.currentTime())} diff --git a/src/features/player/player.tsx b/src/features/player/player.tsx index aab9adf..b426e07 100644 --- a/src/features/player/player.tsx +++ b/src/features/player/player.tsx @@ -75,7 +75,14 @@ export const Player: Component = (props) => { // : ""; // }); - // createEffect(on(thumbnails, (thumbnails) => {})); + // createEffect( + // on( + // () => props.entry, + // (entry) => { + // console.log(entry); + // } + // ) + // ); return ( <> @@ -86,8 +93,6 @@ export const Player: Component = (props) => { props.entry["offset"] ? `#t=${props.entry["offset"]}` : "" }`} poster={props.entry.image} - lang="en" - autoplay > {/* => { "use server"; - const cookies = Object.fromEntries( - getRequestEvent()! - .request.headers.get("cookie")! - .split(";") - .map((c) => c.trim()) - .map((cookie) => { - const index = cookie.indexOf("="); - - return [ - cookie.slice(0, index), - decodeURIComponent(cookie.slice(index + 1)), - ]; - }) - ); - const session = await auth.api.getSession(getRequestEvent()!.request); if (session === null) { return undefined; } - const { - preferred_username, - name, - email, - image = null, - ...user - } = session.user; + const { username, name, email, image = null } = session.user; - return { username: preferred_username, name, email, image }; + return { username, name, email, image }; }, "session"); export const route = { @@ -52,18 +31,14 @@ export default function ShellPage(props: ParentProps) { const user = createAsync(() => load()); const themeContext = useTheme(); - createEffect(() => { - console.log(user()); - }); - - createEffect( - on( - () => themeContext.theme.colorScheme, - (colorScheme) => { - document.documentElement.dataset.theme = colorScheme; - } - ) - ); + // createEffect( + // on( + // () => themeContext.theme.colorScheme, + // (colorScheme) => { + // document.documentElement.dataset.theme = colorScheme; + // } + // ) + // ); return ( diff --git a/src/routes/(shell)/play/[slug].tsx b/src/routes/(shell)/play/[slug].tsx index 1900a29..cc08380 100644 --- a/src/routes/(shell)/play/[slug].tsx +++ b/src/routes/(shell)/play/[slug].tsx @@ -7,7 +7,7 @@ import { RouteDefinition, useParams, } from "@solidjs/router"; -import { Show } from "solid-js"; +import { createEffect, Show } from "solid-js"; import { createSlug, getEntryFromSlug } from "~/features/content"; import { Player } from "~/features/player"; import { Title } from "@solidjs/meta"; @@ -54,10 +54,10 @@ export default function Item() { return (
- {entry()?.title} {(entry) => ( <> + {entry().title} )} diff --git a/src/routes/api/auth/[...action].ts b/src/routes/api/auth/[...action].ts index 826d1ce..dfae5e8 100644 --- a/src/routes/api/auth/[...action].ts +++ b/src/routes/api/auth/[...action].ts @@ -1,4 +1,4 @@ -import { auth } from "~/auth"; +import { auth } from "~/auth.server"; import { toSolidStartHandler } from "better-auth/solid-start"; export const { GET, POST } = toSolidStartHandler(auth); diff --git a/src/routes/sitemap.xml.ts b/src/routes/sitemap.xml.ts index 4cdfeea..898a3e8 100644 --- a/src/routes/sitemap.xml.ts +++ b/src/routes/sitemap.xml.ts @@ -1,7 +1,7 @@ import { SitemapStream, streamToPromise } from 'sitemap' -import { App } from 'vinxi'; +import { App, } from 'vinxi'; -const BASE_URL = 'https://ca-euw-prd-calque-app.purplecoast-f5b7f657.westeurope.azurecontainerapps.io'; +const BASE_URL = 'http://localhost:3000'; export async function GET() { @@ -22,7 +22,13 @@ export async function GET() { } const getRoutes = async () => { - const router = ((globalThis as any).app as App).getRouter('client').internals.routes; + const app = (globalThis as any).app as App; + + const kaas = app.getRouter('client'); + + console.log(kaas.internals); + + const router = app.getRouter('client').internals?.routes; if (router === undefined) { return []; diff --git a/tsconfig.json b/tsconfig.json index 4b4fbe5..3709ff6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ESNext", "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "jsx": "preserve",