Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
#!/usr/bin/env sh

# Run our custom commit-msg hook first for better error messages
.git/hooks/commit-msg "$1"

# If our custom hook passes, then run commitlint for additional validation
if [ $? -eq 0 ]; then
npx --no -- commitlint --edit $1
fi
# Run commitlint for commit message validation
npx --no -- commitlint --edit $1
102 changes: 102 additions & 0 deletions app/api/v2/streaming/auth/session/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { NextRequest, NextResponse } from "next/server";
import {
authenticateWalletSimple,
hasActiveStream,
getUserStreamInfo,
} from "@/lib/streaming/auth-utils";

/**
* GET /api/v2/streaming/auth/session
* Get current streaming session status for authenticated wallet
*/
export async function GET(request: NextRequest) {
try {
const authResult = await authenticateWalletSimple(request);

if (!authResult.isValid) {
return NextResponse.json(
{
error: authResult.error || "Authentication failed",
success: false,
},
{ status: 401 }
);
}

const streamInfo = await getUserStreamInfo(authResult.walletAddress);
const hasActive = await hasActiveStream(authResult.walletAddress);

return NextResponse.json({
success: true,
user: authResult.user,
walletAddress: authResult.walletAddress,
streamInfo: {
hasStream: streamInfo.hasStream,
isLive: streamInfo.isLive,
hasActiveStream: hasActive,
streamId: streamInfo.streamId,
playbackId: streamInfo.playbackId,
},
});
} catch (error) {
console.error("Session check error:", error);
return NextResponse.json(
{
error: "Internal server error",
success: false,
},
{ status: 500 }
);
}
}

/**
* POST /api/v2/streaming/auth/session
* Create or refresh streaming session
*/
export async function POST(request: NextRequest) {
try {
const authResult = await authenticateWalletSimple(request);

if (!authResult.isValid) {
return NextResponse.json(
{
error: authResult.error || "Authentication failed",
success: false,
},
{ status: 401 }
);
}

// Check if user already has an active stream
const hasActive = await hasActiveStream(authResult.walletAddress);

if (hasActive) {
return NextResponse.json(
{
error: "User already has an active stream",
success: false,
hasActiveStream: true,
},
{ status: 409 }
);
}

return NextResponse.json({
success: true,
message: "Streaming session ready",
user: authResult.user,
walletAddress: authResult.walletAddress,
canCreateStream: true,
});
} catch (error) {
console.error("Session creation error:", error);
return NextResponse.json(
{
error: "Internal server error",
success: false,
},
{ status: 500 }
);
}
}
77 changes: 77 additions & 0 deletions app/api/v2/streaming/auth/verify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { NextRequest, NextResponse } from "next/server";
import {
authenticateWallet,
generateAuthMessage,
} from "@/lib/streaming/auth-utils";

/**
* POST /api/v2/streaming/auth/verify
* Verify wallet signature for streaming authentication
*/
export async function POST(request: NextRequest) {
try {
const authResult = await authenticateWallet(request);

if (!authResult.isValid) {
return NextResponse.json(
{
error: authResult.error || "Authentication failed",
success: false,
},
{ status: 401 }
);
}

return NextResponse.json({
success: true,
message: "Wallet authenticated successfully",
user: authResult.user,
walletAddress: authResult.walletAddress,
});
} catch (error) {
console.error("Auth verification error:", error);
return NextResponse.json(
{
error: "Internal server error",
success: false,
},
{ status: 500 }
);
}
}

/**
* GET /api/v2/streaming/auth/verify
* Get authentication message for wallet signing
*/
export async function GET(request: NextRequest) {
try {
const walletAddress =
request.headers.get("x-wallet-address") ||
request.headers.get("wallet-address");

if (!walletAddress) {
return NextResponse.json(
{ error: "Wallet address is required" },
{ status: 400 }
);
}

const authMessage = generateAuthMessage(walletAddress);
const timestamp = Date.now();

return NextResponse.json({
message: authMessage,
timestamp,
walletAddress,
instructions:
"Sign this message with your wallet to authenticate for streaming",
});
} catch (error) {
console.error("Auth message generation error:", error);
return NextResponse.json(
{ error: "Failed to generate authentication message" },
{ status: 500 }
);
}
}
135 changes: 135 additions & 0 deletions app/api/v2/streaming/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { NextResponse } from "next/server";
import { livepeerService } from "@/lib/streaming/livepeer-service";
import { sql } from "@vercel/postgres";

/**
* GET /api/v2/streaming/health
* Health check endpoint for the streaming service
*/
export async function GET() {
try {
const healthChecks: {
timestamp: string;
service: string;
version: string;
status: string;
checks: {
database: { status: string; message: string };
livepeer: { status: string; message: string };
environment: { status: string; message: string };
};
stats?: {
totalUsers: number;
totalStreams: number;
liveStreams: number;
};
} = {
timestamp: new Date().toISOString(),
service: "StreamFi V2 Streaming API",
version: "1.0.0",
status: "healthy",
checks: {
database: { status: "unknown", message: "" },
livepeer: { status: "unknown", message: "" },
environment: { status: "unknown", message: "" },
},
};

// Check database connectivity
try {
await sql`SELECT 1`;
healthChecks.checks.database = {
status: "healthy",
message: "Database connection successful",
};
} catch (error) {
healthChecks.checks.database = {
status: "unhealthy",
message: `Database connection failed: ${error instanceof Error ? error.message : "Unknown error"}`,
};
healthChecks.status = "unhealthy";
}

// Check Livepeer service
try {
const isConfigured = await livepeerService.validateConfiguration();
if (isConfigured) {
healthChecks.checks.livepeer = {
status: "healthy",
message: "Livepeer service is configured and accessible",
};
} else {
healthChecks.checks.livepeer = {
status: "unhealthy",
message: "Livepeer service is not properly configured",
};
healthChecks.status = "unhealthy";
}
} catch (error) {
healthChecks.checks.livepeer = {
status: "unhealthy",
message: `Livepeer service error: ${error instanceof Error ? error.message : "Unknown error"}`,
};
healthChecks.status = "unhealthy";
}

// Check environment variables
const requiredEnvVars = ["LIVEPEER_API_KEY", "DATABASE_URL"];
const missingEnvVars = requiredEnvVars.filter(
envVar => !process.env[envVar]
);

if (missingEnvVars.length === 0) {
healthChecks.checks.environment = {
status: "healthy",
message: "All required environment variables are set",
};
} else {
healthChecks.checks.environment = {
status: "unhealthy",
message: `Missing environment variables: ${missingEnvVars.join(", ")}`,
};
healthChecks.status = "unhealthy";
}

// Get basic statistics
try {
const userCount = await sql`SELECT COUNT(*) as count FROM users`;
const streamCount =
await sql`SELECT COUNT(*) as count FROM users WHERE livepeer_stream_id_v2 IS NOT NULL`;
const liveCount =
await sql`SELECT COUNT(*) as count FROM users WHERE is_live_v2 = true`;

healthChecks.stats = {
totalUsers: parseInt(userCount.rows[0]?.count || "0"),
totalStreams: parseInt(streamCount.rows[0]?.count || "0"),
liveStreams: parseInt(liveCount.rows[0]?.count || "0"),
};
} catch {
// Stats are optional, don't fail the health check
healthChecks.stats = {
totalUsers: 0,
totalStreams: 0,
liveStreams: 0,
};
}

const statusCode = healthChecks.status === "healthy" ? 200 : 503;

return NextResponse.json(healthChecks, { status: statusCode });
} catch (error) {
console.error("Health check error:", error);

return NextResponse.json(
{
timestamp: new Date().toISOString(),
service: "StreamFi V2 Streaming API",
version: "1.0.0",
status: "unhealthy",
error: "Health check failed",
message: error instanceof Error ? error.message : "Unknown error",
},
{ status: 503 }
);
}
}
Loading