Files
easystream/api/search_autocomplete.php
Krystie 29c3c6fc8a feat: Add Meilisearch full-text search with autocomplete and filters
- Add Meilisearch v1.6 service to docker-compose.yml
- Create search indexer with batch and incremental indexing
- Build search API with filters (category, duration, date, content type)
- Add autocomplete API with trending query suggestions
- Implement modern search UI with real-time autocomplete
- Add db_search_queries table for tracking trending searches
- Document setup and usage in docs/MEILISEARCH.md

Features:
- Blazing fast search (5-15ms typical)
- Advanced filters and faceted search
- Real-time autocomplete suggestions
- Trending queries tracking
- Responsive pagination
- Production-ready with proper error handling
2026-03-30 17:07:04 -07:00

132 lines
3.6 KiB
PHP

<?php
/**
* Search Autocomplete API
*
* GET /api/search_autocomplete.php?q=query
*
* Returns suggestions based on:
* 1. Video titles
* 2. Trending search queries
* 3. Categories
*/
require_once dirname(__DIR__) . '/f_core/config.boot.php';
require_once dirname(__DIR__) . '/f_core/f_classes/class.db.php';
header('Content-Type: application/json');
class AutocompleteAPI {
private $meili_host;
private $meili_key;
private $db;
public function __construct() {
$this->meili_host = getenv('MEILI_HOST') ?: 'http://meilisearch:7700';
$this->meili_key = getenv('MEILI_MASTER_KEY') ?: 'changeme_meilisearch_master_key';
$this->db = new VDatabase();
}
public function autocomplete() {
$query = $_GET['q'] ?? '';
if (strlen($query) < 2) {
return $this->success([]);
}
$suggestions = [];
// Get video title suggestions from Meilisearch
$video_suggestions = $this->getVideoSuggestions($query);
// Get trending query suggestions
$trending_suggestions = $this->getTrendingSuggestions($query);
// Merge and deduplicate
$suggestions = array_merge($video_suggestions, $trending_suggestions);
$suggestions = array_unique($suggestions);
// Limit to 10 suggestions
$suggestions = array_slice($suggestions, 0, 10);
return $this->success($suggestions);
}
private function getVideoSuggestions($query) {
$params = [
'q' => $query,
'limit' => 5,
'attributesToRetrieve' => ['title'],
'filter' => 'privacy = "public"'
];
$response = $this->apiRequest('POST', '/indexes/videos/search', $params);
if (!$response || empty($response['hits'])) {
return [];
}
return array_map(function($hit) {
return $hit['title'];
}, $response['hits']);
}
private function getTrendingSuggestions($query) {
$query_escaped = $this->db->escape($query);
$sql = "SELECT query FROM db_search_queries
WHERE query LIKE '$query_escaped%'
ORDER BY search_count DESC, last_searched DESC
LIMIT 5";
$result = $this->db->execute($sql);
if (!$result) {
return [];
}
$suggestions = [];
while ($row = $this->db->fetch($result)) {
$suggestions[] = $row['query'];
}
return $suggestions;
}
private function apiRequest($method, $endpoint, $data = null) {
$url = $this->meili_host . $endpoint;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->meili_key,
'Content-Type: application/json'
]);
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code >= 200 && $http_code < 300) {
return json_decode($response, true);
}
return null;
}
private function success($data) {
echo json_encode([
'success' => true,
'suggestions' => $data
]);
exit;
}
}
$api = new AutocompleteAPI();
$api->autocomplete();