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">
|
<div className="h-full flex flex-col bg-black border-l border-white/10">
|
||||||
{/* Profile Header */}
|
{/* Profile Header */}
|
||||||
<div className="p-4 border-b border-white/10">
|
<div className="p-4 border-b border-white/10">
|
||||||
{/* Avatar and Live Badge */}
|
{/* Name and Live Badge */}
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
{user.image ? (
|
<div className="flex-1 min-w-0">
|
||||||
<div className="relative">
|
<div className="flex items-center gap-2">
|
||||||
<img
|
<h2 className="text-lg font-bold text-white truncate">{displayName}</h2>
|
||||||
src={user.image}
|
|
||||||
alt={displayName}
|
|
||||||
className="w-16 h-16 rounded-full bg-white/10"
|
|
||||||
/>
|
|
||||||
{isLive && (
|
{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
|
Live
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
<p className="text-sm text-white/60">@{user.username}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -151,6 +151,21 @@ export type StreamRecording = z.infer<typeof StreamRecording>
|
|||||||
*/
|
*/
|
||||||
export const StreamRecordingList = co.list(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
|
* Viewer account root - stores any viewer-specific data
|
||||||
*/
|
*/
|
||||||
@@ -163,6 +178,8 @@ export const ViewerRoot = co.map({
|
|||||||
glideCanvas: GlideCanvasList,
|
glideCanvas: GlideCanvasList,
|
||||||
/** Live stream recordings */
|
/** Live stream recordings */
|
||||||
streamRecordings: StreamRecordingList,
|
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
|
* 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 {
|
interface StreamChunk {
|
||||||
streamId: string
|
streamId: string
|
||||||
|
|||||||
@@ -66,6 +66,32 @@ export const Route = createFileRoute("/api/streams/$username/check-hls")({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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 database = getDb(resolveDatabaseUrl())
|
||||||
|
|
||||||
const user = await database.query.users.findFirst({
|
const user = await database.query.users.findFirst({
|
||||||
|
|||||||
Reference in New Issue
Block a user