Shorten video URLs for improved sharing and user experience
Refactors video routing and client-side components to generate and utilize shortened, 8-character IDs derived from the full UUIDs. This change enhances the usability of shared video links by removing dashes and truncating the UUID. The backend now supports fetching videos by either the short or full ID, ensuring backward compatibility. Additionally, ad retrieval and view count updates correctly use the full video ID to maintain data integrity with Bunny.net and the database. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 2cd2c0bc-434c-4bc9-ad3f-b99d3897a0d1 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/2cd2c0bc-434c-4bc9-ad3f-b99d3897a0d1/HCAS0JG
This commit is contained in:
parent
49fbc393a6
commit
258383ce36
@ -22,8 +22,10 @@ export default function NetflixGrid({ videos, isLoading }: NetflixGridProps) {
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
const handleVideoClick = (video: Video) => {
|
||||
// Navigate to individual video page instead of modal
|
||||
setLocation(`/video/${video.id}`);
|
||||
// Generate short ID for cleaner URLs (first 8 chars without dashes)
|
||||
const shortId = video.id.replace(/-/g, '').substring(0, 8);
|
||||
// Navigate to individual video page with short ID
|
||||
setLocation(`/video/${shortId}`);
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
|
||||
@ -44,6 +44,8 @@ function formatDate(date: Date | string): string {
|
||||
}
|
||||
|
||||
export default function VideoCard({ video, onClick, className = "", hideOverlay = false }: VideoCardProps) {
|
||||
// Generate short ID for cleaner URLs (first 8 chars without dashes)
|
||||
const shortId = video.id.replace(/-/g, '').substring(0, 8);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [isMuted, setIsMuted] = useState(true);
|
||||
@ -172,7 +174,7 @@ export default function VideoCard({ video, onClick, className = "", hideOverlay
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid={`card-video-${video.id}`}
|
||||
data-testid={`card-video-${shortId}`}
|
||||
className={`video-card transition-transform duration-200 ${isMobile ? '' : 'hover:scale-[1.02]'} ${className}`}
|
||||
onMouseEnter={() => !isMobile && setIsHovered(true)}
|
||||
onMouseLeave={() => !isMobile && setIsHovered(false)}
|
||||
@ -191,7 +193,7 @@ export default function VideoCard({ video, onClick, className = "", hideOverlay
|
||||
objectPosition: video.faceCenterPosition || 'center center',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
data-testid={`img-thumbnail-${video.id}`}
|
||||
data-testid={`img-thumbnail-${shortId}`}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
onError={(e) => {
|
||||
|
||||
@ -352,10 +352,34 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// Get single video by ID
|
||||
// Generate short ID from long UUID
|
||||
function generateShortId(longId: string): string {
|
||||
// Take first 8 characters and remove dashes for shorter, cleaner URLs
|
||||
return longId.replace(/-/g, '').substring(0, 8);
|
||||
}
|
||||
|
||||
// Find video by short or long ID
|
||||
async function findVideoByAnyId(id: string) {
|
||||
// If it's already a full UUID, use it directly
|
||||
if (id.length === 36 && id.includes('-')) {
|
||||
return await storage.getVideo(id);
|
||||
}
|
||||
|
||||
// If it's a short ID (8 chars), search for matching video
|
||||
if (id.length === 8) {
|
||||
const allVideos = await storage.getVideos({ limit: 200, offset: 0 });
|
||||
const video = allVideos.videos.find(v => generateShortId(v.id) === id);
|
||||
return video;
|
||||
}
|
||||
|
||||
// Try as full ID anyway
|
||||
return await storage.getVideo(id);
|
||||
}
|
||||
|
||||
// Get single video by ID (supports both short and long IDs)
|
||||
app.get("/api/videos/:id", async (req, res) => {
|
||||
try {
|
||||
const video = await storage.getVideo(req.params.id);
|
||||
const video = await findVideoByAnyId(req.params.id);
|
||||
if (!video) {
|
||||
return res.status(404).json({ message: "Video not found" });
|
||||
}
|
||||
@ -370,8 +394,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// Check if video exists first
|
||||
const video = await storage.getVideo(id);
|
||||
// Check if video exists first (supports short and long IDs)
|
||||
const video = await findVideoByAnyId(id);
|
||||
if (!video) {
|
||||
return res.status(404).json({ message: "Video not found" });
|
||||
}
|
||||
@ -381,7 +405,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
try {
|
||||
const { BunnyService } = await import("./bunny");
|
||||
const bunnyService = new BunnyService();
|
||||
ads = await bunnyService.getVideoAds(id);
|
||||
ads = await bunnyService.getVideoAds(video.id); // Use full ID for API calls
|
||||
console.log(`Retrieved ${ads.length} ad spots for video ${id}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to get ads from Bunny.net for video ${id}:`, error);
|
||||
@ -400,10 +424,16 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// Update video views
|
||||
// Update video views (supports short and long IDs)
|
||||
app.post("/api/videos/:id/view", async (req, res) => {
|
||||
try {
|
||||
await storage.updateVideoViews(req.params.id);
|
||||
const video = await findVideoByAnyId(req.params.id);
|
||||
if (!video) {
|
||||
return res.status(404).json({ message: "Video not found" });
|
||||
}
|
||||
|
||||
// Use the full video ID for storage operations
|
||||
await storage.updateVideoViews(video.id);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Failed to update views" });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user