worked on various things
This commit is contained in:
parent
f3cb35653e
commit
3c35b89250
18 changed files with 156 additions and 126 deletions
|
@ -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"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
BIN
auth.sqlite
BIN
auth.sqlite
Binary file not shown.
7
better-auth_migrations/2025-06-17T07-59-07.464Z.sql
Normal file
7
better-auth_migrations/2025-06-17T07-59-07.464Z.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" integer not null, "image" text, "createdAt" date not null, "updatedAt" date not null, "preferred_username" text not null, "username" text not null);
|
||||
|
||||
create table "session" ("id" text not null primary key, "expiresAt" date not null, "token" text not null unique, "createdAt" date not null, "updatedAt" date not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id"));
|
||||
|
||||
create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id"), "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" date, "refreshTokenExpiresAt" date, "scope" text, "password" text, "createdAt" date not null, "updatedAt" date not null);
|
||||
|
||||
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date, "updatedAt" date);
|
23
flake.nix
Normal file
23
flake.nix
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
description = "Streamarr";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgsnixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
bix = {
|
||||
url = "github:knarkzel/bix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
packages = bix.buildBunPackage {
|
||||
src = ./.;
|
||||
packages = ./package.json;
|
||||
};
|
||||
});
|
||||
}
|
6
src/auth.client.ts
Normal file
6
src/auth.client.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { createAuthClient } from "better-auth/solid";
|
||||
import { genericOAuthClient } from "better-auth/client/plugins";
|
||||
|
||||
export const { signIn, signOut, useSession, ...client } = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
});
|
|
@ -1,7 +1,6 @@
|
|||
import { betterAuth } from "better-auth";
|
||||
import { genericOAuth } from "better-auth/plugins";
|
||||
import { createAuthClient } from "better-auth/solid";
|
||||
import { genericOAuthClient } from "better-auth/client/plugins";
|
||||
import { profile } from "bun:jsc";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
export const auth = betterAuth({
|
||||
|
@ -20,18 +19,10 @@ export const auth = betterAuth({
|
|||
type: "string",
|
||||
nullable: true,
|
||||
},
|
||||
preferred_username: {
|
||||
type: "string",
|
||||
nullable: true,
|
||||
},
|
||||
username: {
|
||||
type: "string",
|
||||
nullable: true,
|
||||
},
|
||||
profile: {
|
||||
type: "string",
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
|
@ -54,12 +45,10 @@ export const auth = betterAuth({
|
|||
],
|
||||
accessType: "offline",
|
||||
pkce: true,
|
||||
mapProfileToUser: ({ id, name, email, image, preferred_username, emailVerified }) =>
|
||||
({ id, name, email, emailVerified, image, username: preferred_username }),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const { signIn, signOut, useSession, ...client } = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
});
|
|
@ -219,7 +219,7 @@ export const getItemStream = query(
|
|||
itemId,
|
||||
},
|
||||
query: {
|
||||
startTimeTicks: userData?.playbackPositionTicks,
|
||||
// startTimeTicks: userData?.playbackPositionTicks,
|
||||
},
|
||||
},
|
||||
parseAs: 'stream',
|
||||
|
@ -392,7 +392,7 @@ 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
|
||||
|
|
|
@ -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<Category[]> => {
|
||||
'use server';
|
||||
|
||||
return [
|
||||
// { label: "Continue", entries: await getContinueWatching(jellyfinUserId) },
|
||||
{
|
||||
|
@ -76,18 +77,24 @@ export const listCategories = query(async (): Promise<Category[]> => {
|
|||
}, "content.categories.list");
|
||||
|
||||
export const getEntryFromSlug = query(
|
||||
async (slug: string): Promise<Entry | undefined> => getEntry(slug.match(/\w+$/)![0]),
|
||||
async (slug: string): Promise<Entry | undefined> => {
|
||||
'use server';
|
||||
|
||||
return getEntry(slug.match(/\w+$/)![0]);
|
||||
},
|
||||
"content.getFromSlug",
|
||||
);
|
||||
|
||||
export const getEntry = query(
|
||||
async (id: Entry["id"]): Promise<Entry | undefined> => {
|
||||
'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);
|
||||
}
|
||||
|
||||
|
@ -98,6 +105,8 @@ export const getEntry = query(
|
|||
|
||||
export const getEntryUserData = query(
|
||||
async (id: string): ReturnType<typeof getItemUserData> => {
|
||||
'use server';
|
||||
|
||||
const table = await lookupTable();
|
||||
const { jellyfin } = table[id] ?? {};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<SeekBarProps> = () => {
|
||||
const video = useVideo();
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => [video.duration(), video.buffered(), video.currentTime()] as const,
|
||||
([duration, buffered, currentTime]) => {
|
||||
console.log({ duration, buffered, currentTime });
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div class={css.container}>
|
||||
<span class={css.time}>{formatTime(video.currentTime())}</span>
|
||||
|
|
|
@ -75,7 +75,14 @@ export const Player: Component<PlayerProps> = (props) => {
|
|||
// : "";
|
||||
// });
|
||||
|
||||
// createEffect(on(thumbnails, (thumbnails) => {}));
|
||||
// createEffect(
|
||||
// on(
|
||||
// () => props.entry,
|
||||
// (entry) => {
|
||||
// console.log(entry);
|
||||
// }
|
||||
// )
|
||||
// );
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -86,8 +93,6 @@ export const Player: Component<PlayerProps> = (props) => {
|
|||
props.entry["offset"] ? `#t=${props.entry["offset"]}` : ""
|
||||
}`}
|
||||
poster={props.entry.image}
|
||||
lang="en"
|
||||
autoplay
|
||||
>
|
||||
{/* <track
|
||||
default
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Show } from "solid-js";
|
||||
import { signIn, signOut, client } from "~/auth";
|
||||
import { signIn, signOut, client } from "~/auth.client";
|
||||
import { Avatar, Profile, User } from "../user";
|
||||
import { ColorSchemePicker } from "../theme";
|
||||
import css from "./top.module.css";
|
||||
|
|
|
@ -67,10 +67,10 @@ const [ThemeContextProvider, useTheme] = createContextProvider<
|
|||
},
|
||||
|
||||
setColorScheme(colorScheme) {
|
||||
updateState({ colorScheme, hue: state.latest!.hue });
|
||||
// updateState({ colorScheme, hue: state.latest!.hue });
|
||||
},
|
||||
setHue(hue) {
|
||||
updateState({ hue, colorScheme: state.latest!.colorScheme });
|
||||
// updateState({ hue, colorScheme: state.latest!.colorScheme });
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Meta } from "@solidjs/meta";
|
|||
import { query, createAsync } from "@solidjs/router";
|
||||
import { createEffect, on, ParentProps } from "solid-js";
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import { auth } from "~/auth";
|
||||
import { auth } from "~/auth.server";
|
||||
import { Shell } from "~/features/shell";
|
||||
import { useTheme } from "~/features/theme";
|
||||
import { User } from "~/features/user";
|
||||
|
@ -10,36 +10,15 @@ import { User } from "~/features/user";
|
|||
const load = query(async (): Promise<User | undefined> => {
|
||||
"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 (
|
||||
<Shell user={user()}>
|
||||
|
|
|
@ -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 (
|
||||
<div class={css.page}>
|
||||
<Title>{entry()?.title}</Title>
|
||||
<Show when={entry()} fallback="Some kind of pretty 404 page I guess">
|
||||
{(entry) => (
|
||||
<>
|
||||
<Title>{entry().title}</Title>
|
||||
<Player entry={entry()} />
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 [];
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue