feat: initial commit
This commit is contained in:
commit
e214116e40
253 changed files with 17406 additions and 0 deletions
162
api/search.php
Normal file
162
api/search.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
namespace App\Handlers;
|
||||
|
||||
require __DIR__ . "/Classes/BaseHandler.php";
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
|
||||
class SearchHandler extends BaseHandler
|
||||
{
|
||||
protected int $cacheTTL = 300;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->initializeCache();
|
||||
}
|
||||
|
||||
public function handleRequest(): void
|
||||
{
|
||||
try {
|
||||
$query = $this->validateAndSanitizeQuery($_GET["q"] ?? null);
|
||||
$types = $this->validateAndSanitizeTypes($_GET["type"] ?? "");
|
||||
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
|
||||
$pageSize = isset($_GET["pageSize"]) ? intval($_GET["pageSize"]) : 10;
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
$cacheKey = $this->generateCacheKey($query, $types, $page, $pageSize);
|
||||
$results = [];
|
||||
|
||||
$results =
|
||||
$this->getCachedResults($cacheKey) ??
|
||||
$this->fetchSearchResults($query, $types, $pageSize, $offset);
|
||||
|
||||
if (empty($results) || empty($results["data"])) {
|
||||
$this->sendResponse(["results" => [], "total" => 0, "page" => $page, "pageSize" => $pageSize], 200);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cacheResults($cacheKey, $results);
|
||||
|
||||
$this->sendResponse(
|
||||
[
|
||||
"results" => $results["data"],
|
||||
"total" => $results["total"],
|
||||
"page" => $page,
|
||||
"pageSize" => $pageSize,
|
||||
],
|
||||
200
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
error_log("Search API Error: " . $e->getMessage());
|
||||
$this->sendErrorResponse("Invalid request. Please check your query and try again.", 400);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateAndSanitizeQuery(?string $query): string
|
||||
{
|
||||
if (empty($query) || !is_string($query)) throw new Exception("Invalid 'q' parameter. Must be a non-empty string.");
|
||||
|
||||
$query = trim($query);
|
||||
|
||||
if (strlen($query) > 255) throw new Exception(
|
||||
"Invalid 'q' parameter. Exceeds maximum length of 255 characters."
|
||||
);
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9\s\-_\'"]+$/', $query)) throw new Exception(
|
||||
"Invalid 'q' parameter. Contains unsupported characters."
|
||||
);
|
||||
|
||||
$query = preg_replace("/\s+/", " ", $query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function validateAndSanitizeTypes(string $rawTypes): ?array
|
||||
{
|
||||
$allowedTypes = ["post", "artist", "genre", "book", "movie", "show"];
|
||||
|
||||
if (empty($rawTypes)) return null;
|
||||
|
||||
$types = array_map(
|
||||
fn($type) => strtolower(
|
||||
trim(htmlspecialchars($type, ENT_QUOTES, "UTF-8"))
|
||||
),
|
||||
explode(",", $rawTypes)
|
||||
);
|
||||
$invalidTypes = array_diff($types, $allowedTypes);
|
||||
|
||||
if (!empty($invalidTypes)) throw new Exception(
|
||||
"Invalid 'type' parameter. Unsupported types: " .
|
||||
implode(", ", $invalidTypes)
|
||||
);
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
private function fetchSearchResults(
|
||||
string $query,
|
||||
?array $types,
|
||||
int $pageSize,
|
||||
int $offset
|
||||
): array {
|
||||
$typesParam =
|
||||
$types && count($types) > 0 ? "%7B" . implode(",", $types) . "%7D" : "";
|
||||
$endpoint = "rpc/search_optimized_index";
|
||||
$queryString =
|
||||
"search_query=" .
|
||||
urlencode($query) .
|
||||
"&page_size={$pageSize}&page_offset={$offset}" .
|
||||
($typesParam ? "&types={$typesParam}" : "");
|
||||
|
||||
$data = $this->makeRequest("GET", "{$endpoint}?{$queryString}");
|
||||
|
||||
$total = count($data) > 0 ? $data[0]["total_count"] : 0;
|
||||
$results = array_map(function ($item) {
|
||||
unset($item["total_count"]);
|
||||
return $item;
|
||||
}, $data);
|
||||
|
||||
return ["data" => $results, "total" => $total];
|
||||
}
|
||||
|
||||
private function generateCacheKey(
|
||||
string $query,
|
||||
?array $types,
|
||||
int $page,
|
||||
int $pageSize
|
||||
): string {
|
||||
$typesKey = $types ? implode(",", $types) : "all";
|
||||
return sprintf(
|
||||
"search:%s:types:%s:page:%d:pageSize:%d",
|
||||
md5($query),
|
||||
$typesKey,
|
||||
$page,
|
||||
$pageSize
|
||||
);
|
||||
}
|
||||
|
||||
private function getCachedResults(string $cacheKey): ?array
|
||||
{
|
||||
if ($this->cache instanceof \Redis) {
|
||||
$cachedData = $this->cache->get($cacheKey);
|
||||
return $cachedData ? json_decode($cachedData, true) : null;
|
||||
} elseif (is_array($this->cache)) {
|
||||
return $this->cache[$cacheKey] ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function cacheResults(string $cacheKey, array $results): void
|
||||
{
|
||||
if ($this->cache instanceof \Redis) {
|
||||
$this->cache->set($cacheKey, json_encode($results));
|
||||
$this->cache->expire($cacheKey, $this->cacheTTL);
|
||||
} elseif (is_array($this->cache)) {
|
||||
$this->cache[$cacheKey] = $results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$handler = new SearchHandler();
|
||||
$handler->handleRequest();
|
Loading…
Add table
Add a link
Reference in a new issue