You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

186 lines
5.4 KiB

import { Response } from 'express';
import { AuthRequest } from '../types/index.js';
import { db, getSetting } from '../config/database.js';
import { formatDuration } from '../services/youtube.service.js';
import { refreshMultipleChannels, isRefreshInProgress, setRefreshInProgress } from '../services/cache.service.js';
export async function getAllVideos(req: AuthRequest, res: Response) {
try {
const { page = 1, limit = 12, channelId, search, sort = 'newest' } = req.query as any;
const pageNum = page as number;
const limitNum = limit as number;
const offset = (pageNum - 1) * limitNum;
// Build query
let whereClause = 'v.duration_seconds >= 600';
const args: any[] = [];
if (channelId) {
whereClause += ' AND v.channel_id = ?';
args.push(channelId);
}
if (search) {
whereClause += ' AND (v.title LIKE ? OR v.description LIKE ?)';
args.push(`%${search}%`, `%${search}%`);
}
// Sort clause
let orderClause = 'v.published_at DESC';
if (sort === 'oldest') {
orderClause = 'v.published_at ASC';
} else if (sort === 'popular') {
orderClause = 'v.view_count DESC';
}
// Get total count
const countResult = await db.execute({
sql: `SELECT COUNT(*) as total FROM videos_cache v WHERE ${whereClause}`,
args
});
const total = countResult.rows[0].total as number;
// Get videos
const videosResult = await db.execute({
sql: `
SELECT
v.*,
c.name as channel_name,
c.thumbnail_url as channel_thumbnail
FROM videos_cache v
JOIN channels c ON v.channel_id = c.id
WHERE ${whereClause}
ORDER BY ${orderClause}
LIMIT ? OFFSET ?
`,
args: [...args, limitNum, offset]
});
// Get oldest cache age
const cacheAgeResult = await db.execute(
'SELECT MIN(last_fetched) as oldest FROM cache_metadata'
);
let oldestCacheAge = 0;
if (cacheAgeResult.rows.length > 0 && cacheAgeResult.rows[0].oldest) {
const oldestFetch = new Date(cacheAgeResult.rows[0].oldest as string);
oldestCacheAge = Math.floor((Date.now() - oldestFetch.getTime()) / 60000);
}
// Check cache age and trigger refresh if needed
const cacheDuration = parseInt((await getSetting('cache_duration_minutes')) || '60');
const cacheExpired = oldestCacheAge > cacheDuration;
const refreshInProgress = await isRefreshInProgress();
// Trigger async refresh if cache is expired and not already refreshing
if (cacheExpired && !refreshInProgress) {
// Fire and forget - don't await
refreshAllChannelsAsync();
}
const videos = videosResult.rows.map(row => ({
id: row.id,
channelId: row.channel_id,
channelName: row.channel_name,
channelThumbnail: row.channel_thumbnail,
title: row.title,
description: row.description,
thumbnailUrl: row.thumbnail_url,
publishedAt: row.published_at,
viewCount: row.view_count,
likeCount: row.like_count,
duration: row.duration,
durationFormatted: formatDuration(row.duration as string)
}));
res.json({
success: true,
data: { videos },
meta: {
page: pageNum,
limit: limitNum,
total,
totalPages: Math.ceil(total / limitNum),
hasMore: offset + videos.length < total,
oldestCacheAge,
cacheStale: cacheExpired,
refreshing: refreshInProgress
}
});
} catch (error: any) {
console.error('Get videos error:', error);
res.status(500).json({
success: false,
error: {
code: 'GET_VIDEOS_ERROR',
message: 'Error fetching videos'
}
});
}
}
export async function refreshVideos(req: AuthRequest, res: Response) {
try {
let channelIds: string[] = req.body.channelIds || [];
// If no specific channels, get all channels
if (channelIds.length === 0) {
const allChannels = await db.execute('SELECT id FROM channels');
channelIds = allChannels.rows.map(row => row.id as string);
}
if (channelIds.length === 0) {
return res.json({
success: true,
data: {
channelsRefreshed: 0,
videosAdded: 0,
videosUpdated: 0,
errors: []
}
});
}
// Refresh channels in parallel
const result = await refreshMultipleChannels(channelIds, true);
res.json({
success: true,
data: {
channelsRefreshed: result.success,
videosAdded: result.videosAdded,
videosUpdated: result.videosAdded, // Since we replace cache, all are "updated"
errors: result.errors
}
});
} catch (error: any) {
console.error('Refresh videos error:', error);
res.status(500).json({
success: false,
error: {
code: 'REFRESH_VIDEOS_ERROR',
message: 'Error refreshing videos'
}
});
}
}
async function refreshAllChannelsAsync() {
try {
await setRefreshInProgress(true);
// Get all channel IDs
const channels = await db.execute('SELECT id FROM channels');
const channelIds = channels.rows.map(row => row.id as string);
if (channelIds.length > 0) {
const result = await refreshMultipleChannels(channelIds, true);
}
} catch (error) {
console.error('[AUTO-REFRESH] Error:', error);
} finally {
await setRefreshInProgress(false);
}
}