package repository import ( "database/sql" "strings" "vidarchive/internal/models" ) type MediaRepository struct { db *sql.DB } func NewMediaRepository(db *sql.DB) *MediaRepository { return &MediaRepository{db: db} } func (r *MediaRepository) Create(m *models.Media) error { result, err := r.db.Exec( `INSERT INTO media (url, title, description, duration, filepath, relative_path, is_audio, has_embedded_thumbnail, info_json_path, playlist_id, playlist_index) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, m.URL, m.Title, m.Description, m.Duration, m.Filepath, m.RelativePath, boolToInt(m.IsAudio), boolToInt(m.HasEmbeddedThumbnail), m.InfoJSONPath, m.PlaylistID, m.PlaylistIndex, ) if err != nil { return err } m.ID, _ = result.LastInsertId() return nil } func (r *MediaRepository) GetByID(id int64) (*models.Media, error) { row := r.db.QueryRow( `SELECT id, url, title, description, duration, filepath, relative_path, is_audio, has_embedded_thumbnail, info_json_path, playlist_id, playlist_index, created_at FROM media WHERE id = ?`, id, ) return scanMedia(row) } func (r *MediaRepository) GetAll(path string, sortBy, filter string) ([]*models.Media, error) { query := `SELECT id, url, title, description, duration, filepath, relative_path, is_audio, has_embedded_thumbnail, info_json_path, playlist_id, playlist_index, created_at FROM media WHERE relative_path LIKE ?` args := []interface{}{path + "%"} if filter != "" { query += ` AND (title LIKE ? OR description LIKE ?)` args = append(args, "%"+filter+"%", "%"+filter+"%") } switch sortBy { case "title": query += ` ORDER BY title` case "date": query += ` ORDER BY created_at DESC` case "duration": query += ` ORDER BY duration DESC` default: query += ` ORDER BY created_at DESC` } rows, err := r.db.Query(query, args...) if err != nil { return nil, err } defer rows.Close() var media []*models.Media for rows.Next() { m, err := scanMedia(rows) if err != nil { return nil, err } media = append(media, m) } return media, rows.Err() } func (r *MediaRepository) GetByRelativePath(relPath string) (*models.Media, error) { row := r.db.QueryRow( `SELECT id, url, title, description, duration, filepath, relative_path, is_audio, has_embedded_thumbnail, info_json_path, playlist_id, playlist_index, created_at FROM media WHERE relative_path = ?`, relPath, ) return scanMedia(row) } func (r *MediaRepository) Delete(id int64) error { _, err := r.db.Exec(`DELETE FROM media WHERE id = ?`, id) return err } func (r *MediaRepository) GetFolders(parentPath string) ([]string, error) { rows, err := r.db.Query( `SELECT relative_path FROM media WHERE relative_path LIKE ?`, parentPath+"%", ) if err != nil { return nil, err } defer rows.Close() folders := make(map[string]bool) for rows.Next() { var relPath string if err := rows.Scan(&relPath); err != nil { return nil, err } // Get the folder part after parentPath remaining := strings.TrimPrefix(relPath, parentPath) remaining = strings.TrimPrefix(remaining, "/") if idx := strings.Index(remaining, "/"); idx >= 0 { folder := remaining[:idx] if folder != "" { folders[parentPath+"/"+folder] = true } } } var result []string for f := range folders { result = append(result, f) } return result, rows.Err() } func scanMedia(row interface{ Scan(...interface{}) error }) (*models.Media, error) { var m models.Media var isAudio, hasThumb int err := row.Scan( &m.ID, &m.URL, &m.Title, &m.Description, &m.Duration, &m.Filepath, &m.RelativePath, &isAudio, &hasThumb, &m.InfoJSONPath, &m.PlaylistID, &m.PlaylistIndex, &m.CreatedAt, ) if err != nil { return nil, err } m.IsAudio = isAudio == 1 m.HasEmbeddedThumbnail = hasThumb == 1 return &m, nil }