diff --git a/backend/src/db/database.ts b/backend/src/db/database.ts index f83cfd1..15169e4 100644 --- a/backend/src/db/database.ts +++ b/backend/src/db/database.ts @@ -126,6 +126,20 @@ export const videoOperations = { .all(status) as Video[]; }, + search: (query: string, status?: string) => { + const searchPattern = `%${query}%`; + + if (status) { + return db.prepare( + 'SELECT * FROM videos WHERE title LIKE ? COLLATE NOCASE AND status = ? ORDER BY addedAt DESC' + ).all(searchPattern, status) as Video[]; + } else { + return db.prepare( + 'SELECT * FROM videos WHERE title LIKE ? COLLATE NOCASE ORDER BY addedAt DESC' + ).all(searchPattern) as Video[]; + } + }, + getNextPending: () => { return db.prepare('SELECT * FROM videos WHERE status = ? ORDER BY addedAt ASC LIMIT 1') .get('pending') as Video | undefined; diff --git a/backend/src/routes/videos.ts b/backend/src/routes/videos.ts index 88bb3aa..b0027d4 100644 --- a/backend/src/routes/videos.ts +++ b/backend/src/routes/videos.ts @@ -9,10 +9,17 @@ const router = Router(); // Get all videos router.get('/', (req, res) => { try { - const { status } = req.query; + const { status, search } = req.query; let videos; - if (status && typeof status === 'string') { + + // If search query is provided, use search + if (search && typeof search === 'string') { + const statusFilter = status && typeof status === 'string' ? status : undefined; + videos = videoOperations.search(search, statusFilter); + } + // Otherwise use status filter or get all + else if (status && typeof status === 'string') { videos = videoOperations.getByStatus(status); } else { videos = videoOperations.getAll(); diff --git a/frontend/src/pages/VideosPage.tsx b/frontend/src/pages/VideosPage.tsx index 60ce19a..f789f74 100644 --- a/frontend/src/pages/VideosPage.tsx +++ b/frontend/src/pages/VideosPage.tsx @@ -10,15 +10,18 @@ function VideosPage() { const [addUrl, setAddUrl] = useState(''); const [adding, setAdding] = useState(false); const [filter, setFilter] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); useEffect(() => { loadVideos(); - }, [filter]); + }, [filter, searchQuery]); const loadVideos = async () => { try { setLoading(true); - const data = filter === 'all' ? await videosAPI.getAll() : await videosAPI.getAll(filter); + const statusFilter = filter === 'all' ? undefined : filter; + const search = searchQuery.trim() || undefined; + const data = await videosAPI.getAll(statusFilter, search); setVideos(data); setError(''); } catch (err) { @@ -127,47 +130,82 @@ function VideosPage() { -
- - - - - +
+
+ + + + + +
+ +
+
+ setSearchQuery(e.target.value)} + placeholder="Search videos by title..." + className="w-full rounded-md border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 pl-10" + /> + + + +
+ {searchQuery && ( + + )} +
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 8a1e835..e84ff22 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -56,9 +56,14 @@ export const channelsAPI = { // Videos API export const videosAPI = { - getAll: (status?: string) => { - const url = status - ? `${API_BASE}/videos?status=${status}` + getAll: (status?: string, search?: string) => { + const params = new URLSearchParams(); + if (status) params.append('status', status); + if (search) params.append('search', search); + + const queryString = params.toString(); + const url = queryString + ? `${API_BASE}/videos?${queryString}` : `${API_BASE}/videos`; return fetchJSON(url); },