worked on various things

This commit is contained in:
Chris Kruining 2025-06-17 16:13:40 +02:00
parent f3cb35653e
commit 3c35b89250
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
18 changed files with 156 additions and 126 deletions

View file

@ -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"],
},
},
});

Binary file not shown.

View 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
View 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
View 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()],
});

View file

@ -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()],
});

View file

@ -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

View file

@ -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,6 +24,8 @@ const lookupTable = query(async () => {
export const getHighlights = () => getContinueWatching(jellyfinUserId);
export const getStream = query(async (id: string, range: string) => {
'use server';
const table = await lookupTable();
const ids = table[id];
const manager = id[0] === 'm' ? 'radarr' : 'sonarr'
@ -59,11 +59,12 @@ export const getStream = query(async (id: string, range: string) => {
const res = await ((manager === 'radarr' ? addMovie : addSeries)(id.slice(1)));
revalidate(lookupTable.keyFor())
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] ?? {};

View file

@ -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;

View file

@ -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>

View file

@ -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

View file

@ -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";

View file

@ -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 });
},
};
},

View file

@ -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()}>

View file

@ -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()} />
</>
)}

View file

@ -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);

View file

@ -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 [];

View file

@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",