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
93 lines
2.9 KiB
TypeScript
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;
|
|
}
|