mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-11 14:30:26 +01:00
Improve billing and access control by adding creator subscription check; update route components to enforce user authentication before viewing streams and replays.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { getFlowgladServer } from "./flowglad"
|
||||
import { getAuth } from "./auth"
|
||||
import { db } from "@/db/connection"
|
||||
import { stripe_subscriptions, storage_usage } from "@/db/schema"
|
||||
import { stripe_subscriptions, storage_usage, creator_subscriptions } from "@/db/schema"
|
||||
import { eq, and, gte, lte } from "drizzle-orm"
|
||||
|
||||
// Usage limits
|
||||
@@ -528,3 +528,27 @@ export async function hasActiveSubscription(userId: string): Promise<boolean> {
|
||||
|
||||
return !!subscription
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has an active subscription to a specific creator (server-side only)
|
||||
*/
|
||||
export async function hasCreatorSubscription(
|
||||
subscriberId: string,
|
||||
creatorId: string
|
||||
): Promise<boolean> {
|
||||
const database = db()
|
||||
|
||||
const [subscription] = await database
|
||||
.select()
|
||||
.from(creator_subscriptions)
|
||||
.where(
|
||||
and(
|
||||
eq(creator_subscriptions.subscriber_id, subscriberId),
|
||||
eq(creator_subscriptions.creator_id, creatorId),
|
||||
eq(creator_subscriptions.status, "active")
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
return !!subscription
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { createFileRoute, Link } from "@tanstack/react-router"
|
||||
import { getStreamByUsername, type StreamPageData } from "@/lib/stream/db"
|
||||
import { VideoPlayer } from "@/components/VideoPlayer"
|
||||
import { CloudflareStreamPlayer } from "@/components/CloudflareStreamPlayer"
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
type SpotifyNowPlayingResponse,
|
||||
} from "@/lib/spotify/now-playing"
|
||||
import { getStreamStatus } from "@/lib/stream/status"
|
||||
import { authClient } from "@/lib/auth-client"
|
||||
|
||||
export const Route = createFileRoute("/$username")({
|
||||
ssr: false,
|
||||
@@ -48,6 +49,7 @@ const NIKIV_DATA: StreamPageData = {
|
||||
|
||||
function StreamPage() {
|
||||
const { username } = Route.useParams()
|
||||
const { data: session, isPending: sessionLoading } = authClient.useSession()
|
||||
const [data, setData] = useState<StreamPageData | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -63,6 +65,8 @@ function StreamPage() {
|
||||
const [showReadyPulse, setShowReadyPulse] = useState(false)
|
||||
const readyPulseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const isAuthenticated = !sessionLoading && !!session?.user
|
||||
|
||||
useEffect(() => {
|
||||
let isActive = true
|
||||
const setReadySafe = (ready: boolean) => {
|
||||
@@ -312,6 +316,34 @@ function StreamPage() {
|
||||
}
|
||||
}, [shouldFetchSpotify])
|
||||
|
||||
// Auth gate - require login to view streams
|
||||
if (sessionLoading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-black text-white">
|
||||
<div className="text-xl">Loading...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-black text-white">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4">Sign in to watch</h1>
|
||||
<p className="text-neutral-400 mb-8">
|
||||
Create an account or sign in to view this stream
|
||||
</p>
|
||||
<Link
|
||||
to="/login"
|
||||
className="inline-block rounded-lg bg-white px-6 py-3 font-medium text-black hover:bg-neutral-200 transition-colors"
|
||||
>
|
||||
Sign in
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-black text-white">
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createFileRoute } from "@tanstack/react-router"
|
||||
import { and, eq } from "drizzle-orm"
|
||||
import { db } from "@/db/connection"
|
||||
import { getAuth } from "@/lib/auth"
|
||||
import { hasActiveSubscription } from "@/lib/billing"
|
||||
import { hasCreatorSubscription } from "@/lib/billing"
|
||||
import { stream_replays, streams } from "@/db/schema"
|
||||
|
||||
const json = (data: unknown, status = 200) =>
|
||||
@@ -99,7 +99,7 @@ const handleGet = async ({
|
||||
return json({ replay })
|
||||
}
|
||||
|
||||
// Non-owners need subscription to view replays
|
||||
// Non-owners need subscription to this creator to view replays
|
||||
if (!session?.user?.id) {
|
||||
return json(
|
||||
{ error: "Subscription required", code: "SUBSCRIPTION_REQUIRED" },
|
||||
@@ -107,7 +107,7 @@ const handleGet = async ({
|
||||
)
|
||||
}
|
||||
|
||||
const hasSubscription = await hasActiveSubscription(session.user.id)
|
||||
const hasSubscription = await hasCreatorSubscription(session.user.id, replay.user_id)
|
||||
if (!hasSubscription) {
|
||||
return json(
|
||||
{ error: "Subscription required", code: "SUBSCRIPTION_REQUIRED" },
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createFileRoute } from "@tanstack/react-router"
|
||||
import { and, desc, eq } from "drizzle-orm"
|
||||
import { db } from "@/db/connection"
|
||||
import { getAuth } from "@/lib/auth"
|
||||
import { hasActiveSubscription } from "@/lib/billing"
|
||||
import { hasCreatorSubscription } from "@/lib/billing"
|
||||
import { stream_replays, users } from "@/db/schema"
|
||||
|
||||
const json = (data: unknown, status = 200) =>
|
||||
@@ -56,7 +56,7 @@ const handleGet = async ({
|
||||
}
|
||||
}
|
||||
|
||||
// Non-owners need subscription to view replays
|
||||
// Non-owners need subscription to this creator to view replays
|
||||
if (!session?.user?.id) {
|
||||
return json(
|
||||
{ error: "Subscription required", code: "SUBSCRIPTION_REQUIRED" },
|
||||
@@ -64,7 +64,7 @@ const handleGet = async ({
|
||||
)
|
||||
}
|
||||
|
||||
const hasSubscription = await hasActiveSubscription(session.user.id)
|
||||
const hasSubscription = await hasCreatorSubscription(session.user.id, user.id)
|
||||
if (!hasSubscription) {
|
||||
return json(
|
||||
{ error: "Subscription required", code: "SUBSCRIPTION_REQUIRED" },
|
||||
|
||||
Reference in New Issue
Block a user