Compare commits
2 Commits
2ac68f9a69
...
224b898bcd
| Author | SHA1 | Date |
|---|---|---|
|
|
224b898bcd | |
|
|
933d12dd14 |
|
|
@ -1,33 +1,78 @@
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import db from '@/db';
|
import db from '@/db';
|
||||||
|
|
||||||
export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
try {
|
try {
|
||||||
const parsedId = parseInt(id);
|
const mediaId = parseInt(id);
|
||||||
|
|
||||||
if (isNaN(parsedId)) {
|
if (isNaN(mediaId)) {
|
||||||
return NextResponse.json({ error: 'Invalid bookmark ID' }, { status: 400 });
|
return NextResponse.json({ error: 'Invalid media ID' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get media_id before deleting
|
// Check if media exists
|
||||||
const bookmark = db.prepare(`
|
const media = db.prepare(`
|
||||||
SELECT media_id FROM bookmarks WHERE id = ?
|
SELECT id FROM media WHERE id = ?
|
||||||
`).get(parsedId) as { media_id: number } | undefined;
|
`).get(mediaId) as { id: number } | undefined;
|
||||||
|
|
||||||
if (!bookmark) {
|
if (!media) {
|
||||||
return NextResponse.json({ error: 'Bookmark not found' }, { status: 404 });
|
return NextResponse.json({ error: 'Media not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete bookmark
|
// Check if already bookmarked
|
||||||
db.prepare(`DELETE FROM bookmarks WHERE id = ?`).run(parsedId);
|
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
|
// Update media bookmark count
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
UPDATE media
|
UPDATE media
|
||||||
SET bookmark_count = (SELECT COUNT(*) FROM bookmarks WHERE media_id = ?)
|
SET bookmark_count = (SELECT COUNT(*) FROM bookmarks WHERE media_id = ?)
|
||||||
WHERE 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 });
|
return NextResponse.json({ success: true });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,37 @@ const VideosPage = () => {
|
||||||
setSelectedVideo(null);
|
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) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen p-6">
|
<div className="min-h-screen p-6">
|
||||||
|
|
@ -218,6 +249,9 @@ const VideosPage = () => {
|
||||||
showBookmarks={true}
|
showBookmarks={true}
|
||||||
showRatings={true}
|
showRatings={true}
|
||||||
formatFileSize={formatFileSize}
|
formatFileSize={formatFileSize}
|
||||||
|
onBookmark={handleBookmark}
|
||||||
|
onUnbookmark={handleUnbookmark}
|
||||||
|
onRate={handleRate}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -160,11 +160,7 @@ export default function InlineVideoPlayer({ video, isOpen, onClose, scrollPositi
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add bookmark
|
// Add bookmark
|
||||||
const response = await fetch('/api/bookmarks', {
|
const response = await fetch(`/api/bookmarks/${video.id}`, { method: 'POST' });
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ mediaId: video.id })
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setIsBookmarked(true);
|
setIsBookmarked(true);
|
||||||
setBookmarkCount(prev => prev + 1);
|
setBookmarkCount(prev => prev + 1);
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,39 @@ export default function PhotoViewer({
|
||||||
}
|
}
|
||||||
}, [isOpen, photo]);
|
}, [isOpen, photo]);
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
handleClose();
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
if (onPrev) {
|
||||||
|
event.preventDefault();
|
||||||
|
handlePrev();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
if (onNext) {
|
||||||
|
event.preventDefault();
|
||||||
|
handleNext();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [isOpen, onNext, onPrev]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setIsPhotoLoading(false);
|
setIsPhotoLoading(false);
|
||||||
onClose();
|
onClose();
|
||||||
|
|
@ -84,19 +117,19 @@ export default function PhotoViewer({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBookmark = () => {
|
const handleBookmark = () => {
|
||||||
if (onBookmark && 'id' in photo) {
|
if (onBookmark && 'id' in photo && photo.id !== undefined) {
|
||||||
onBookmark(photo.id);
|
onBookmark(photo.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnbookmark = () => {
|
const handleUnbookmark = () => {
|
||||||
if (onUnbookmark && 'id' in photo) {
|
if (onUnbookmark && 'id' in photo && photo.id !== undefined) {
|
||||||
onUnbookmark(photo.id);
|
onUnbookmark(photo.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRate = (rating: number) => {
|
const handleRate = (rating: number) => {
|
||||||
if (onRate && 'id' in photo) {
|
if (onRate && 'id' in photo && photo.id !== undefined) {
|
||||||
onRate(photo.id, rating);
|
onRate(photo.id, rating);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -130,6 +163,11 @@ export default function PhotoViewer({
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPhotoId = () => {
|
||||||
|
if ('id' in photo && photo.id !== undefined) return photo.id;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
if (!isOpen || typeof window === 'undefined') return null;
|
if (!isOpen || typeof window === 'undefined') return null;
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
|
|
@ -175,7 +213,7 @@ export default function PhotoViewer({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={`/api/photos/${('id' in photo ? photo.id : photo.id) || ''}`}
|
src={`/api/photos/${getPhotoId()}`}
|
||||||
alt={getPhotoTitle()}
|
alt={getPhotoTitle()}
|
||||||
className={`max-w-full max-h-[90vh] w-auto h-auto object-contain rounded-lg ${isPhotoLoading ? 'hidden' : ''}`}
|
className={`max-w-full max-h-[90vh] w-auto h-auto object-contain rounded-lg ${isPhotoLoading ? 'hidden' : ''}`}
|
||||||
onLoad={() => setIsPhotoLoading(false)}
|
onLoad={() => setIsPhotoLoading(false)}
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,20 @@ export default function VideoViewer({
|
||||||
const [currentTime, setCurrentTime] = useState(0);
|
const [currentTime, setCurrentTime] = useState(0);
|
||||||
const [duration, setDuration] = useState(0);
|
const [duration, setDuration] = useState(0);
|
||||||
const [showControls, setShowControls] = useState(true);
|
const [showControls, setShowControls] = useState(true);
|
||||||
|
const [isBookmarked, setIsBookmarked] = useState(false);
|
||||||
|
const [bookmarkCount, setBookmarkCount] = useState(0);
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
|
||||||
|
// Update local bookmark state when video changes
|
||||||
useEffect(() => {
|
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.src = `/api/stream/${('id' in video ? video.id : video.id) || ''}`;
|
||||||
videoRef.current.load();
|
videoRef.current.load();
|
||||||
|
|
||||||
|
|
@ -133,30 +143,37 @@ export default function VideoViewer({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBookmark = () => {
|
const handleBookmark = () => {
|
||||||
if (onBookmark && 'id' in video) {
|
if (onBookmark && video && 'id' in video && video.id !== undefined) {
|
||||||
onBookmark(video.id);
|
onBookmark(video.id);
|
||||||
|
// Update local state immediately
|
||||||
|
setIsBookmarked(true);
|
||||||
|
setBookmarkCount(prev => prev + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnbookmark = () => {
|
const handleUnbookmark = () => {
|
||||||
if (onUnbookmark && 'id' in video) {
|
if (onUnbookmark && video && 'id' in video && video.id !== undefined) {
|
||||||
onUnbookmark(video.id);
|
onUnbookmark(video.id);
|
||||||
|
// Update local state immediately
|
||||||
|
setIsBookmarked(false);
|
||||||
|
setBookmarkCount(prev => Math.max(0, prev - 1));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRate = (rating: number) => {
|
const handleRate = (rating: number) => {
|
||||||
if (onRate && 'id' in video) {
|
if (onRate && video && 'id' in video && video.id !== undefined) {
|
||||||
onRate(video.id, rating);
|
onRate(video.id, rating);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVideoTitle = () => {
|
const getVideoTitle = () => {
|
||||||
if ('title' in video) return video.title;
|
if (video && 'title' in video) return video.title;
|
||||||
if ('name' in video) return video.name;
|
if (video && 'name' in video) return video.name;
|
||||||
return 'Video';
|
return 'Video';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVideoSize = () => {
|
const getVideoSize = () => {
|
||||||
|
if (!video) return '0 Bytes';
|
||||||
if (formatFileSize) {
|
if (formatFileSize) {
|
||||||
return formatFileSize(video.size);
|
return formatFileSize(video.size);
|
||||||
}
|
}
|
||||||
|
|
@ -170,12 +187,17 @@ export default function VideoViewer({
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBookmarkCount = () => {
|
const getBookmarkCount = () => {
|
||||||
if ('bookmark_count' in video) return video.bookmark_count;
|
if (video && 'bookmark_count' in video) return video.bookmark_count;
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAvgRating = () => {
|
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;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -214,7 +236,7 @@ export default function VideoViewer({
|
||||||
onMouseMove={() => setShowControls(true)}
|
onMouseMove={() => setShowControls(true)}
|
||||||
onMouseLeave={() => setShowControls(false)}
|
onMouseLeave={() => setShowControls(false)}
|
||||||
>
|
>
|
||||||
<source src={`/api/stream/${('id' in video ? video.id : video.id) || ''}`} type="video/mp4" />
|
<source src={`/api/stream/${getVideoId()}`} type="video/mp4" />
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
|
@ -226,10 +248,10 @@ export default function VideoViewer({
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{showBookmarks && (
|
{showBookmarks && (
|
||||||
<button
|
<button
|
||||||
onClick={getBookmarkCount() > 0 ? handleUnbookmark : handleBookmark}
|
onClick={isBookmarked ? handleUnbookmark : handleBookmark}
|
||||||
className="flex items-center gap-1 text-white hover:text-yellow-400 transition-colors"
|
className="flex items-center gap-1 text-white hover:text-yellow-400 transition-colors"
|
||||||
>
|
>
|
||||||
<Bookmark className={`h-4 w-4 ${getBookmarkCount() > 0 ? 'fill-yellow-400 text-yellow-400' : ''}`} />
|
<Bookmark className={`h-4 w-4 ${isBookmarked ? 'fill-yellow-400 text-yellow-400' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{showRatings && (
|
{showRatings && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue