videofolxtv/server/routes.ts
sebastjanartic 0458b42937 Display video previews correctly for videos with restricted access
Implements a thumbnail proxy to fetch private BunnyCDN video thumbnails.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aa92e7e2-ec62-4c92-b21b-02ef78a664c2
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/aa92e7e2-ec62-4c92-b21b-02ef78a664c2/PiJtjmP
2025-08-04 20:20:29 +00:00

93 lines
2.9 KiB
TypeScript

import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import { z } from "zod";
export async function registerRoutes(app: Express): Promise<Server> {
// Get videos with pagination and filtering
app.get("/api/videos", async (req, res) => {
try {
const limit = parseInt(req.query.limit as string) || 20;
const offset = parseInt(req.query.offset as string) || 0;
const search = req.query.search as string;
const videos = await storage.getVideos(limit, offset, search);
const total = await storage.getVideoCount(search);
res.json({
videos,
total,
hasMore: offset + limit < total
});
} catch (error) {
res.status(500).json({ message: "Failed to fetch videos" });
}
});
// Get single video by ID
app.get("/api/videos/:id", async (req, res) => {
try {
const video = await storage.getVideo(req.params.id);
if (!video) {
return res.status(404).json({ message: "Video not found" });
}
res.json(video);
} catch (error) {
res.status(500).json({ message: "Failed to fetch video" });
}
});
// Update video views
app.post("/api/videos/:id/view", async (req, res) => {
try {
await storage.updateVideoViews(req.params.id);
res.json({ success: true });
} catch (error) {
res.status(500).json({ message: "Failed to update views" });
}
});
// Proxy endpoint for thumbnails (since they are private)
app.get("/thumbnail/:id", async (req, res) => {
try {
const { id } = req.params;
const apiKey = process.env.BUNNY_API_KEY;
const libraryId = process.env.BUNNY_LIBRARY_ID;
if (!apiKey || !libraryId) {
return res.status(500).json({ message: "Bunny.net configuration missing" });
}
// Try to get thumbnail from Bunny API
const thumbnailUrl = `https://video.bunnycdn.com/library/${libraryId}/videos/${id}/thumbnail`;
const response = await fetch(thumbnailUrl, {
method: 'POST',
headers: {
'AccessKey': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ time: 10 }) // Get thumbnail at 10 seconds
});
if (response.ok) {
const thumbnailData = await response.arrayBuffer();
res.set('Content-Type', 'image/jpeg');
res.set('Cache-Control', 'public, max-age=3600'); // Cache for 1 hour
res.send(Buffer.from(thumbnailData));
} else {
// If thumbnail generation fails, return a placeholder
res.redirect('https://via.placeholder.com/480x270/1a1a1a/ffffff?text=Video');
}
} catch (error) {
console.error('Error proxying thumbnail:', error);
res.redirect('https://via.placeholder.com/480x270/1a1a1a/ffffff?text=Video');
}
});
const httpServer = createServer(app);
return httpServer;
}