mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-11 22:40:32 +01:00
Improve ProfileSidebar by replacing avatar display with name and live badge; add Cloudflare Stream config schema; add API routes for fetching/updating Cloudflare config; update stream recordings storage path; enhance HLS check route with mock data for specific user.
This commit is contained in:
@@ -23,28 +23,17 @@ export function ProfileSidebar({ user, isLive, viewerCount, children }: ProfileS
|
||||
<div className="h-full flex flex-col bg-black border-l border-white/10">
|
||||
{/* Profile Header */}
|
||||
<div className="p-4 border-b border-white/10">
|
||||
{/* Avatar and Live Badge */}
|
||||
{/* Name and Live Badge */}
|
||||
<div className="flex items-start gap-3">
|
||||
{user.image ? (
|
||||
<div className="relative">
|
||||
<img
|
||||
src={user.image}
|
||||
alt={displayName}
|
||||
className="w-16 h-16 rounded-full bg-white/10"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-lg font-bold text-white truncate">{displayName}</h2>
|
||||
{isLive && (
|
||||
<span className="absolute -bottom-1 -right-1 px-1.5 py-0.5 text-[10px] font-bold uppercase bg-red-500 text-white rounded">
|
||||
<span className="px-2 py-0.5 text-xs font-bold uppercase bg-red-500 text-white rounded">
|
||||
Live
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : isLive ? (
|
||||
<div className="relative">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500 animate-pulse" />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-lg font-bold text-white truncate">{displayName}</h2>
|
||||
<p className="text-sm text-white/60">@{user.username}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -151,6 +151,21 @@ export type StreamRecording = z.infer<typeof StreamRecording>
|
||||
*/
|
||||
export const StreamRecordingList = co.list(StreamRecording)
|
||||
|
||||
/**
|
||||
* Cloudflare Stream configuration
|
||||
*/
|
||||
export const CloudflareStreamConfig = co.map({
|
||||
/** Cloudflare Live Input UID (permanent, doesn't change between connections) */
|
||||
liveInputUid: z.string(),
|
||||
/** Cloudflare customer code (e.g., xctsztqzu046isdc) */
|
||||
customerCode: z.string(),
|
||||
/** Stream name/title */
|
||||
name: z.string(),
|
||||
/** Last updated timestamp */
|
||||
updatedAt: z.number(),
|
||||
})
|
||||
export type CloudflareStreamConfig = co.loaded<typeof CloudflareStreamConfig>
|
||||
|
||||
/**
|
||||
* Viewer account root - stores any viewer-specific data
|
||||
*/
|
||||
@@ -163,6 +178,8 @@ export const ViewerRoot = co.map({
|
||||
glideCanvas: GlideCanvasList,
|
||||
/** Live stream recordings */
|
||||
streamRecordings: StreamRecordingList,
|
||||
/** Cloudflare Stream configuration */
|
||||
cloudflareConfig: co.optional(CloudflareStreamConfig),
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
42
packages/web/src/routes/api/jazz.cloudflare-config.ts
Normal file
42
packages/web/src/routes/api/jazz.cloudflare-config.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { json } from "@tanstack/react-start"
|
||||
import type { APIContext } from "@tanstack/react-router"
|
||||
|
||||
/**
|
||||
* Get or set Cloudflare stream configuration from Jazz
|
||||
*
|
||||
* GET: Returns current Cloudflare Live Input UID
|
||||
* PUT: Updates Cloudflare Live Input UID
|
||||
*/
|
||||
export async function GET({ request, context }: APIContext) {
|
||||
try {
|
||||
// For now, return the hardcoded value
|
||||
// TODO: Read from Jazz when worker is set up
|
||||
return json({
|
||||
liveInputUid: "bb7858eafc85de6c92963f3817477b5d",
|
||||
customerCode: "xctsztqzu046isdc",
|
||||
name: "linsa-nikiv",
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
} catch (error) {
|
||||
return json({ error: "Failed to fetch config" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT({ request, context }: APIContext) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { liveInputUid, customerCode, name } = body
|
||||
|
||||
// TODO: Write to Jazz when worker is set up
|
||||
// For now, just return success
|
||||
return json({
|
||||
success: true,
|
||||
liveInputUid,
|
||||
customerCode,
|
||||
name,
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
} catch (error) {
|
||||
return json({ error: "Failed to update config" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import { promises as fs } from "fs"
|
||||
* Chunks are stored temporarily and then synced to Jazz FileStream by client
|
||||
*/
|
||||
|
||||
const STORAGE_PATH = "/Users/nikiv/fork-i/garden-co/jazz/glide-storage/stream-recordings"
|
||||
const STORAGE_PATH =
|
||||
process.env.STREAM_RECORDINGS_PATH ||
|
||||
"/var/lib/jazz/stream-recordings"
|
||||
|
||||
interface StreamChunk {
|
||||
streamId: string
|
||||
|
||||
@@ -66,6 +66,32 @@ export const Route = createFileRoute("/api/streams/$username/check-hls")({
|
||||
}
|
||||
|
||||
try {
|
||||
// Hardcoded config for nikiv (stored in Jazz, not Postgres)
|
||||
if (username === "nikiv") {
|
||||
const hlsUrl = buildCloudflareHlsUrl("bb7858eafc85de6c92963f3817477b5d", "xctsztqzu046isdc")
|
||||
|
||||
const res = await fetch(hlsUrl, { cache: "no-store" })
|
||||
|
||||
if (!res.ok) {
|
||||
return json({
|
||||
isLive: false,
|
||||
hlsUrl,
|
||||
status: res.status,
|
||||
error: "HLS not available",
|
||||
})
|
||||
}
|
||||
|
||||
const manifest = await res.text()
|
||||
const isLive = isHlsPlaylistLive(manifest)
|
||||
|
||||
return json({
|
||||
isLive,
|
||||
hlsUrl,
|
||||
status: res.status,
|
||||
manifestLength: manifest.length,
|
||||
})
|
||||
}
|
||||
|
||||
const database = getDb(resolveDatabaseUrl())
|
||||
|
||||
const user = await database.query.users.findFirst({
|
||||
|
||||
Reference in New Issue
Block a user