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:
sebastjanartic 2025-09-03 11:03:27 +00:00
parent 49fbc393a6
commit 258383ce36
3 changed files with 45 additions and 11 deletions

View File

@ -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 = () => {

View File

@ -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) => {

View File

@ -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" });