Restored to 'e1314807be287b395cffa7ada2c736a4a7831e11'

Replit-Restored-To: e1314807be
This commit is contained in:
sebastjanartic 2025-09-06 19:38:50 +00:00
parent d71f6d7f28
commit 72295f7fe8
4 changed files with 38 additions and 157 deletions

View File

@ -30,9 +30,7 @@ export default function AdSenseAd({
}, []);
const adStyle: React.CSSProperties = {
display: 'block',
minWidth: '300px',
minHeight: '250px'
display: 'block'
};
if (adFormat !== 'auto' && width && height) {

View File

@ -230,7 +230,7 @@ function CategoryRow({ category, onVideoClick, hideScrollButtons = false }: Cate
// Calculate card width with gap for precise scrolling
const containerWidth = scrollRef.current.clientWidth;
const isMobile = window.innerWidth < 768;
const cardWidth = isMobile ? containerWidth - 24 : 280; // 24px for mobile margins
const cardWidth = isMobile ? containerWidth - 24 : 330; // 24px for mobile margins (3rem - 1.5rem each side)
const gap = 12; // 3 * 0.25rem = 12px gap
const scrollAmount = cardWidth + gap;
@ -424,7 +424,7 @@ function CategoryRow({ category, onVideoClick, hideScrollButtons = false }: Cate
{category.videos.map((video, index) => (
<div
key={video.id}
className="flex-shrink-0 w-[calc(100vw-1.5rem)] sm:w-[280px] relative hover:z-50"
className="flex-shrink-0 w-[calc(100vw-2.5rem)] sm:w-[330px] relative hover:z-50"
style={{
scrollSnapAlign: window.innerWidth < 768 ? 'start' : 'none'
}}

View File

@ -30,7 +30,7 @@ export async function findVideoByAnyId(id: string) {
// If it's an 8-character short ID, find by short ID
if (id.length === 8) {
const allVideos = await storage.getVideos(1000, 0);
const allVideos = await storage.getVideos(200, 0);
return allVideos.find(v => v.id.replace(/-/g, '').substring(0, 8) === id);
}
@ -48,63 +48,24 @@ declare module "express-session" {
}
}
// Ensure upload directory exists
const uploadDir = './uploads/videos';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
console.log('📁 Created upload directory:', uploadDir);
}
// Configure multer for video uploads with better error handling
// Configure multer for video uploads
const upload = multer({
storage: multer.diskStorage({
destination: (req, file, cb) => {
try {
// Ensure directory exists
if (!fs.existsSync('./uploads/videos')) {
fs.mkdirSync('./uploads/videos', { recursive: true });
}
cb(null, './uploads/videos');
} catch (error) {
console.error('❌ Error creating upload directory:', error);
cb(error as Error, './uploads/videos');
}
},
destination: './uploads/videos',
filename: (req, file, cb) => {
try {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const filename = file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname);
cb(null, filename);
} catch (error) {
console.error('❌ Error generating filename:', error);
cb(error as Error, file.originalname);
}
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
}),
limits: {
fileSize: 500 * 1024 * 1024, // 500MB max file size
fieldSize: 1024 * 1024, // 1MB field size
fields: 10,
files: 1
},
fileFilter: (req, file, cb) => {
try {
// Allow video files only with strict validation
const allowedMimes = [
'video/mp4', 'video/mpeg', 'video/quicktime', 'video/x-msvideo',
'video/webm', 'video/ogg', 'video/3gpp', 'video/x-flv'
];
if (file.mimetype.startsWith('video/') && allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
const error = new Error(`Unsupported video format: ${file.mimetype}. Only video files are allowed.`);
console.error('❌ File filter error:', error.message);
cb(error);
}
} catch (error) {
console.error('❌ Error in file filter:', error);
cb(error as Error);
// Allow video files only
if (file.mimetype.startsWith('video/')) {
cb(null, true);
} else {
cb(new Error('Only video files are allowed'));
}
}
});
@ -540,104 +501,47 @@ export async function registerRoutes(app: Express): Promise<Server> {
});
}
// Validate file size (max 500MB)
const parsedFileSize = parseInt(fileSize);
if (isNaN(parsedFileSize) || parsedFileSize <= 0 || parsedFileSize > 500 * 1024 * 1024) {
return res.status(400).json({
message: "Invalid file size. Maximum file size is 500MB"
});
}
// Validate MIME type
const allowedMimes = [
'video/mp4', 'video/mpeg', 'video/quicktime', 'video/x-msvideo',
'video/webm', 'video/ogg', 'video/3gpp', 'video/x-flv'
];
if (!mimeType.startsWith('video/') || !allowedMimes.includes(mimeType)) {
return res.status(400).json({
message: `Unsupported video format: ${mimeType}. Only video files are allowed.`
});
}
const uploadData = {
userId: req.session.userId!,
originalFileName,
fileSize: parsedFileSize,
fileSize: parseInt(fileSize),
mimeType,
uploadStatus: "uploading" as const,
uploadProgress: 0
};
const upload = await storage.createVideoUpload(uploadData);
console.log(`📤 Upload initialized: ${upload.id} for file: ${originalFileName}`);
res.status(201).json(upload);
} catch (error) {
console.error('❌ Failed to initialize upload:', error);
res.status(500).json({
message: error instanceof Error ? error.message : "Failed to initialize upload"
});
res.status(500).json({ message: "Failed to initialize upload" });
}
});
app.post("/api/uploads/:id/video", authenticate, upload.single('video'), async (req, res) => {
let uploadId: string | null = null;
try {
uploadId = req.params.id;
const uploadId = req.params.id;
const file = req.file;
console.log(`📤 Processing video upload for ID: ${uploadId}`);
if (!file) {
console.error('❌ No video file provided in request');
return res.status(400).json({ message: "No video file provided" });
}
// Validate upload ID
if (!uploadId || uploadId.trim() === '') {
console.error('❌ Invalid upload ID provided');
return res.status(400).json({ message: "Invalid upload ID" });
}
// Check if upload record exists
const existingUpload = await storage.getVideoUpload(uploadId);
if (!existingUpload) {
console.error(`❌ Upload record not found for ID: ${uploadId}`);
return res.status(404).json({ message: "Upload record not found" });
}
console.log(`📁 File uploaded: ${file.originalname}, size: ${file.size} bytes`);
// Update upload with file information
await storage.updateVideoUpload(uploadId, {
uploadStatus: "processing",
uploadProgress: 1.0
});
// Parse tags safely
let tags: string[] = [];
if (req.body.tags) {
try {
tags = JSON.parse(req.body.tags);
if (!Array.isArray(tags)) {
tags = [];
}
} catch (parseError) {
console.warn('⚠️ Failed to parse tags, using empty array:', parseError);
tags = [];
}
}
// Create video record with safe data validation
// Create video record
const videoData = {
title: req.body.title?.trim() || path.parse(file.originalname).name,
description: req.body.description?.trim() || "",
thumbnailUrl: req.body.thumbnailUrl?.trim() || "https://via.placeholder.com/800x450",
title: req.body.title || path.parse(file.originalname).name,
description: req.body.description || "",
thumbnailUrl: req.body.thumbnailUrl || "https://via.placeholder.com/800x450",
videoUrl: `/uploads/videos/${file.filename}`,
duration: Math.max(0, parseInt(req.body.duration) || 0),
duration: parseInt(req.body.duration) || 0,
views: 0,
category: req.body.category?.trim() || "",
tags: tags,
category: req.body.category || "",
tags: req.body.tags ? JSON.parse(req.body.tags) : [],
isPublic: req.body.isPublic !== "false",
uploadStatus: "completed",
originalFileName: file.originalname,
@ -645,7 +549,6 @@ export async function registerRoutes(app: Express): Promise<Server> {
format: path.extname(file.originalname).slice(1)
};
console.log(`🎬 Creating video record: ${videoData.title}`);
const video = await storage.createVideo(videoData);
// Link video to upload
@ -654,33 +557,19 @@ export async function registerRoutes(app: Express): Promise<Server> {
uploadStatus: "completed"
});
console.log(`✅ Video upload completed successfully: ${video.id}`);
res.json({
video,
upload: { id: uploadId, status: "completed" },
message: "Video uploaded successfully"
});
res.json({ video, upload: { id: uploadId, status: "completed" } });
} catch (error) {
console.error("Upload error:", error);
console.error("Upload error:", error);
// Update upload status to failed if we have an uploadId
if (uploadId) {
try {
await storage.updateVideoUpload(uploadId, {
uploadStatus: "failed",
errorMessage: error instanceof Error ? error.message : "Upload failed"
});
} catch (updateError) {
console.error("❌ Failed to update upload status:", updateError);
}
// Update upload status to failed
if (req.params.id) {
await storage.updateVideoUpload(req.params.id, {
uploadStatus: "failed",
errorMessage: error instanceof Error ? error.message : "Upload failed"
});
}
// Return appropriate error response
const errorMessage = error instanceof Error ? error.message : "Failed to upload video";
res.status(500).json({
message: errorMessage,
uploadId: uploadId
});
res.status(500).json({ message: "Failed to upload video" });
}
});

View File

@ -166,21 +166,15 @@ class VideoSyncService {
try {
await this.syncVideos();
// Add timeout protection for database sync with longer timeout
// Add timeout protection for database sync
const syncTimeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Database sync timeout after 60 seconds')), 60000)
setTimeout(() => reject(new Error('Database sync timeout after 30 seconds')), 30000)
);
try {
await Promise.race([
this.syncVideosToDatabase(),
syncTimeout
]);
} catch (error) {
console.error('❌ Database sync failed with timeout:', error);
console.log('⚠️ Continuing with cached data only - videos will still be available');
// Don't throw the error, just log it and continue
}
await Promise.race([
this.syncVideosToDatabase(),
syncTimeout
]);
this.startPeriodicSync();
console.log('✅ Video sync service initialized successfully');