mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
278 lines
8.1 KiB
Go
278 lines
8.1 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/trackeep/backend/models"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// VideoBookmarkService handles video bookmark operations
|
|
type VideoBookmarkService struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewVideoBookmarkService creates a new video bookmark service
|
|
func NewVideoBookmarkService(db *gorm.DB) *VideoBookmarkService {
|
|
return &VideoBookmarkService{db: db}
|
|
}
|
|
|
|
// VideoInfo represents video information from scraper
|
|
type VideoInfo struct {
|
|
VideoID string `json:"video_id"`
|
|
Title string `json:"title"`
|
|
Channel string `json:"channel"`
|
|
Thumbnail string `json:"thumbnail_url"`
|
|
Success bool `json:"success"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// SaveVideoRequest represents the request to save a video
|
|
type SaveVideoRequest struct {
|
|
URL string `json:"url" binding:"required"`
|
|
Description string `json:"description"`
|
|
Tags string `json:"tags"`
|
|
IsFavorite bool `json:"is_favorite"`
|
|
}
|
|
|
|
// SaveVideoBookmark saves a video bookmark
|
|
func (vbs *VideoBookmarkService) SaveVideoBookmark(userID uint, req SaveVideoRequest) (*models.VideoBookmark, error) {
|
|
// Extract video info using scraper
|
|
videoInfo, err := vbs.extractVideoInfo(req.URL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to extract video info: %w", err)
|
|
}
|
|
|
|
if !videoInfo.Success {
|
|
return nil, fmt.Errorf("scraper error: %s", videoInfo.Error)
|
|
}
|
|
|
|
// Check if video already bookmarked by this user
|
|
var existingBookmark models.VideoBookmark
|
|
if err := vbs.db.Where("user_id = ? AND video_id = ?", userID, videoInfo.VideoID).First(&existingBookmark).Error; err == nil {
|
|
return nil, fmt.Errorf("video already bookmarked")
|
|
}
|
|
|
|
// Create bookmark
|
|
bookmark := models.VideoBookmark{
|
|
VideoID: videoInfo.VideoID,
|
|
Title: videoInfo.Title,
|
|
Channel: videoInfo.Channel,
|
|
Thumbnail: videoInfo.Thumbnail,
|
|
URL: req.URL,
|
|
UserID: userID,
|
|
Description: req.Description,
|
|
Tags: req.Tags,
|
|
IsFavorite: req.IsFavorite,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
if err := vbs.db.Create(&bookmark).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to save bookmark: %w", err)
|
|
}
|
|
|
|
return &bookmark, nil
|
|
}
|
|
|
|
// GetUserBookmarks gets all bookmarks for a user
|
|
func (vbs *VideoBookmarkService) GetUserBookmarks(userID uint, limit int, offset int) ([]models.VideoBookmark, error) {
|
|
var bookmarks []models.VideoBookmark
|
|
|
|
query := vbs.db.Where("user_id = ?", userID).Order("created_at DESC")
|
|
|
|
if limit > 0 {
|
|
query = query.Limit(limit)
|
|
}
|
|
|
|
if offset > 0 {
|
|
query = query.Offset(offset)
|
|
}
|
|
|
|
if err := query.Find(&bookmarks).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get bookmarks: %w", err)
|
|
}
|
|
|
|
return bookmarks, nil
|
|
}
|
|
|
|
// GetBookmarkByID gets a bookmark by ID
|
|
func (vbs *VideoBookmarkService) GetBookmarkByID(userID uint, bookmarkID uint) (*models.VideoBookmark, error) {
|
|
var bookmark models.VideoBookmark
|
|
if err := vbs.db.Where("id = ? AND user_id = ?", bookmarkID, userID).First(&bookmark).Error; err != nil {
|
|
return nil, fmt.Errorf("bookmark not found: %w", err)
|
|
}
|
|
return &bookmark, nil
|
|
}
|
|
|
|
// UpdateBookmark updates a bookmark
|
|
func (vbs *VideoBookmarkService) UpdateBookmark(userID uint, bookmarkID uint, req SaveVideoRequest) (*models.VideoBookmark, error) {
|
|
bookmark, err := vbs.GetBookmarkByID(userID, bookmarkID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Update fields
|
|
bookmark.Description = req.Description
|
|
bookmark.Tags = req.Tags
|
|
bookmark.IsFavorite = req.IsFavorite
|
|
bookmark.UpdatedAt = time.Now()
|
|
|
|
if err := vbs.db.Save(bookmark).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to update bookmark: %w", err)
|
|
}
|
|
|
|
return bookmark, nil
|
|
}
|
|
|
|
// DeleteBookmark deletes a bookmark
|
|
func (vbs *VideoBookmarkService) DeleteBookmark(userID uint, bookmarkID uint) error {
|
|
result := vbs.db.Where("id = ? AND user_id = ?", bookmarkID, userID).Delete(&models.VideoBookmark{})
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to delete bookmark: %w", result.Error)
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return fmt.Errorf("bookmark not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ToggleWatched toggles the watched status of a bookmark
|
|
func (vbs *VideoBookmarkService) ToggleWatched(userID uint, bookmarkID uint) (*models.VideoBookmark, error) {
|
|
bookmark, err := vbs.GetBookmarkByID(userID, bookmarkID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bookmark.IsWatched = !bookmark.IsWatched
|
|
bookmark.UpdatedAt = time.Now()
|
|
|
|
if err := vbs.db.Save(bookmark).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to update bookmark: %w", err)
|
|
}
|
|
|
|
return bookmark, nil
|
|
}
|
|
|
|
// ToggleFavorite toggles the favorite status of a bookmark
|
|
func (vbs *VideoBookmarkService) ToggleFavorite(userID uint, bookmarkID uint) (*models.VideoBookmark, error) {
|
|
bookmark, err := vbs.GetBookmarkByID(userID, bookmarkID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bookmark.IsFavorite = !bookmark.IsFavorite
|
|
bookmark.UpdatedAt = time.Now()
|
|
|
|
if err := vbs.db.Save(bookmark).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to update bookmark: %w", err)
|
|
}
|
|
|
|
return bookmark, nil
|
|
}
|
|
|
|
// SearchBookmarks searches bookmarks by title, channel, or tags
|
|
func (vbs *VideoBookmarkService) SearchBookmarks(userID uint, query string, limit int, offset int) ([]models.VideoBookmark, error) {
|
|
var bookmarks []models.VideoBookmark
|
|
|
|
searchQuery := "%" + query + "%"
|
|
dbQuery := vbs.db.Where("user_id = ? AND (title LIKE ? OR channel LIKE ? OR tags LIKE ?)",
|
|
userID, searchQuery, searchQuery, searchQuery).Order("created_at DESC")
|
|
|
|
if limit > 0 {
|
|
dbQuery = dbQuery.Limit(limit)
|
|
}
|
|
|
|
if offset > 0 {
|
|
dbQuery = dbQuery.Offset(offset)
|
|
}
|
|
|
|
if err := dbQuery.Find(&bookmarks).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to search bookmarks: %w", err)
|
|
}
|
|
|
|
return bookmarks, nil
|
|
}
|
|
|
|
// GetBookmarkStats gets statistics about user's bookmarks
|
|
func (vbs *VideoBookmarkService) GetBookmarkStats(userID uint) (map[string]interface{}, error) {
|
|
stats := make(map[string]interface{})
|
|
|
|
// Total bookmarks
|
|
var total int64
|
|
if err := vbs.db.Model(&models.VideoBookmark{}).Where("user_id = ?", userID).Count(&total).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get total count: %w", err)
|
|
}
|
|
stats["total"] = total
|
|
|
|
// Watched bookmarks
|
|
var watched int64
|
|
if err := vbs.db.Model(&models.VideoBookmark{}).Where("user_id = ? AND is_watched = ?", userID, true).Count(&watched).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get watched count: %w", err)
|
|
}
|
|
stats["watched"] = watched
|
|
|
|
// Favorite bookmarks
|
|
var favorites int64
|
|
if err := vbs.db.Model(&models.VideoBookmark{}).Where("user_id = ? AND is_favorite = ?", userID, true).Count(&favorites).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get favorites count: %w", err)
|
|
}
|
|
stats["favorites"] = favorites
|
|
|
|
// Unwatched bookmarks
|
|
stats["unwatched"] = total - watched
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// extractVideoInfo extracts video information using the scraper service
|
|
func (vbs *VideoBookmarkService) extractVideoInfo(url string) (*VideoInfo, error) {
|
|
// In demo mode, create mock data
|
|
if os.Getenv("VITE_DEMO_MODE") == "true" {
|
|
return &VideoInfo{
|
|
VideoID: "demo123",
|
|
Title: "Demo Video Title",
|
|
Channel: "Demo Channel",
|
|
Thumbnail: "https://i.ytimg.com/vi/demo123/hqdefault.jpg",
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// Call the scraper service
|
|
scraperURL := fmt.Sprintf("http://youtube-video-scraper:7858/video")
|
|
|
|
req, err := http.NewRequest("POST", scraperURL, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create request body
|
|
reqBody := fmt.Sprintf(`{"url": "%s"}`, url)
|
|
req.Body = nil // Will be set below
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Post(scraperURL, "application/json", strings.NewReader(reqBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call scraper service: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("scraper service returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
var videoInfo VideoInfo
|
|
if err := json.NewDecoder(resp.Body).Decode(&videoInfo); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &videoInfo, nil
|
|
}
|