From 224b898bcd3404f5a645da3db185a8ab7487d117 Mon Sep 17 00:00:00 2001 From: tigeren Date: Tue, 26 Aug 2025 18:42:09 +0000 Subject: [PATCH] feat: implement bookmarking and unbookmarking functionality for videos - Added POST and DELETE endpoints for managing bookmarks in the video API. - Enhanced the VideosPage component to handle bookmarking and unbookmarking actions. - Updated the InlineVideoPlayer and VideoViewer components to reflect bookmark state and count. - Improved error handling for invalid media IDs and existing bookmarks. --- media.db | Bin 258048 -> 258048 bytes src/app/api/bookmarks/[id]/route.ts | 71 ++++++++++++++++++++----- src/app/videos/page.tsx | 34 ++++++++++++ src/components/inline-video-player.tsx | 6 +-- src/components/video-viewer.tsx | 44 +++++++++++---- 5 files changed, 126 insertions(+), 29 deletions(-) diff --git a/media.db b/media.db index 07b7ff4851fa30b7c1332ec7d9b17706a90928fd..fbd3112827d990f018f4bf8fdfc5bcab4e012180 100644 GIT binary patch delta 460 zcmZvWze@sP7{}k|J@4|>?D3!wEa;Xtt=GHL>>wN(0)zSk8q#PGB!n(n)fyTrY-rSI zsHx-~8uLF;8k-CanxhCTuhX#j#7mH zGmJk8Cbzi$JHd=uJW**C%`e~{_Jmq`#E>ijQ2BFfiq9>0A^3*%^0v2L-5GLyjd~`S-y$nRZGY_Nk4_=;d_^sHic)Isf%Gh4Nv)}K4GxWOOkzn`(LjQS| delta 301 zcmZp8z~AtIe}Xil#zYxsMvaXLi}d-PGqCUPxW21cg31{S(TW(o#oRt82^riNIAc!8!d@?T)!zW~&IjDPZldJ~{&zZv*{ zZx)>JgnwcHFFy-2BPSCVSRIBqGfxFCsf^QVX0Op}cs3pMjUY3F~!2*ga= K`JXU< }) { +export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) { const { id } = await params; try { - const parsedId = parseInt(id); + const mediaId = parseInt(id); - if (isNaN(parsedId)) { - return NextResponse.json({ error: 'Invalid bookmark ID' }, { status: 400 }); + if (isNaN(mediaId)) { + return NextResponse.json({ error: 'Invalid media ID' }, { status: 400 }); } - // Get media_id before deleting - const bookmark = db.prepare(` - SELECT media_id FROM bookmarks WHERE id = ? - `).get(parsedId) as { media_id: number } | undefined; + // Check if media exists + const media = db.prepare(` + SELECT id FROM media WHERE id = ? + `).get(mediaId) as { id: number } | undefined; - if (!bookmark) { - return NextResponse.json({ error: 'Bookmark not found' }, { status: 404 }); + if (!media) { + return NextResponse.json({ error: 'Media not found' }, { status: 404 }); } - // Delete bookmark - db.prepare(`DELETE FROM bookmarks WHERE id = ?`).run(parsedId); + // Check if already bookmarked + const existing = db.prepare(` + SELECT id FROM bookmarks WHERE media_id = ? + `).get(mediaId); + + if (existing) { + return NextResponse.json({ error: 'Already bookmarked' }, { status: 400 }); + } + + // Insert bookmark + const result = db.prepare(` + INSERT INTO bookmarks (media_id) VALUES (?) + `).run(mediaId); // Update media bookmark count db.prepare(` UPDATE media SET bookmark_count = (SELECT COUNT(*) FROM bookmarks WHERE media_id = ?) WHERE id = ? - `).run(bookmark.media_id, bookmark.media_id); + `).run(mediaId, mediaId); + + return NextResponse.json({ id: result.lastInsertRowid }); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + try { + const mediaId = parseInt(id); + + if (isNaN(mediaId)) { + return NextResponse.json({ error: 'Invalid media ID' }, { status: 400 }); + } + + // Check if bookmark exists + const bookmark = db.prepare(` + SELECT id FROM bookmarks WHERE media_id = ? + `).get(mediaId) as { id: number } | undefined; + + if (!bookmark) { + return NextResponse.json({ error: 'Bookmark not found' }, { status: 404 }); + } + + // Delete bookmark + db.prepare(`DELETE FROM bookmarks WHERE media_id = ?`).run(mediaId); + + // Update media bookmark count + db.prepare(` + UPDATE media + SET bookmark_count = (SELECT COUNT(*) FROM bookmarks WHERE media_id = ?) + WHERE id = ? + `).run(mediaId, mediaId); return NextResponse.json({ success: true }); } catch (error: any) { diff --git a/src/app/videos/page.tsx b/src/app/videos/page.tsx index 327f596..194074d 100644 --- a/src/app/videos/page.tsx +++ b/src/app/videos/page.tsx @@ -67,6 +67,37 @@ const VideosPage = () => { setSelectedVideo(null); }; + const handleBookmark = async (videoId: number) => { + try { + await fetch(`/api/bookmarks/${videoId}`, { method: 'POST' }); + fetchVideos(); + } catch (error) { + console.error('Error bookmarking video:', error); + } + }; + + const handleUnbookmark = async (videoId: number) => { + try { + await fetch(`/api/bookmarks/${videoId}`, { method: 'DELETE' }); + fetchVideos(); + } catch (error) { + console.error('Error unbookmarking video:', error); + } + }; + + const handleRate = async (videoId: number, rating: number) => { + try { + await fetch(`/api/stars/${videoId}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ rating }) + }); + fetchVideos(); + } catch (error) { + console.error('Error rating video:', error); + } + }; + if (loading) { return (
@@ -218,6 +249,9 @@ const VideosPage = () => { showBookmarks={true} showRatings={true} formatFileSize={formatFileSize} + onBookmark={handleBookmark} + onUnbookmark={handleUnbookmark} + onRate={handleRate} /> ); diff --git a/src/components/inline-video-player.tsx b/src/components/inline-video-player.tsx index c5dd85f..6bc0648 100644 --- a/src/components/inline-video-player.tsx +++ b/src/components/inline-video-player.tsx @@ -160,11 +160,7 @@ export default function InlineVideoPlayer({ video, isOpen, onClose, scrollPositi } } else { // Add bookmark - const response = await fetch('/api/bookmarks', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ mediaId: video.id }) - }); + const response = await fetch(`/api/bookmarks/${video.id}`, { method: 'POST' }); if (response.ok) { setIsBookmarked(true); setBookmarkCount(prev => prev + 1); diff --git a/src/components/video-viewer.tsx b/src/components/video-viewer.tsx index 7165931..d2b8dff 100644 --- a/src/components/video-viewer.tsx +++ b/src/components/video-viewer.tsx @@ -56,10 +56,20 @@ export default function VideoViewer({ const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [showControls, setShowControls] = useState(true); + const [isBookmarked, setIsBookmarked] = useState(false); + const [bookmarkCount, setBookmarkCount] = useState(0); const videoRef = useRef(null); + // Update local bookmark state when video changes useEffect(() => { - if (isOpen && videoRef.current) { + if (video && 'bookmark_count' in video) { + setIsBookmarked(video.bookmark_count > 0); + setBookmarkCount(video.bookmark_count); + } + }, [video]); + + useEffect(() => { + if (isOpen && videoRef.current && video) { videoRef.current.src = `/api/stream/${('id' in video ? video.id : video.id) || ''}`; videoRef.current.load(); @@ -133,30 +143,37 @@ export default function VideoViewer({ }; const handleBookmark = () => { - if (onBookmark && 'id' in video) { + if (onBookmark && video && 'id' in video && video.id !== undefined) { onBookmark(video.id); + // Update local state immediately + setIsBookmarked(true); + setBookmarkCount(prev => prev + 1); } }; const handleUnbookmark = () => { - if (onUnbookmark && 'id' in video) { + if (onUnbookmark && video && 'id' in video && video.id !== undefined) { onUnbookmark(video.id); + // Update local state immediately + setIsBookmarked(false); + setBookmarkCount(prev => Math.max(0, prev - 1)); } }; const handleRate = (rating: number) => { - if (onRate && 'id' in video) { + if (onRate && video && 'id' in video && video.id !== undefined) { onRate(video.id, rating); } }; const getVideoTitle = () => { - if ('title' in video) return video.title; - if ('name' in video) return video.name; + if (video && 'title' in video) return video.title; + if (video && 'name' in video) return video.name; return 'Video'; }; const getVideoSize = () => { + if (!video) return '0 Bytes'; if (formatFileSize) { return formatFileSize(video.size); } @@ -170,12 +187,17 @@ export default function VideoViewer({ }; const getBookmarkCount = () => { - if ('bookmark_count' in video) return video.bookmark_count; + if (video && 'bookmark_count' in video) return video.bookmark_count; return 0; }; const getAvgRating = () => { - if ('avg_rating' in video) return video.avg_rating; + if (video && 'avg_rating' in video) return video.avg_rating; + return 0; + }; + + const getVideoId = () => { + if (video && 'id' in video && video.id !== undefined) return video.id; return 0; }; @@ -214,7 +236,7 @@ export default function VideoViewer({ onMouseMove={() => setShowControls(true)} onMouseLeave={() => setShowControls(false)} > - + Your browser does not support the video tag. @@ -226,10 +248,10 @@ export default function VideoViewer({
{showBookmarks && ( )} {showRatings && (