Files
Tomas Dvorak d27cf14110 first test
2026-02-08 14:14:55 +01:00

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
}