mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
searcg revamp 😧
This commit is contained in:
@@ -723,16 +723,400 @@ class AdminController extends AdminBaseController
|
||||
public function searchActors(Request $request, Response $response, $args)
|
||||
{
|
||||
$query = $request->getQueryParams()['q'] ?? '';
|
||||
|
||||
|
||||
if (empty($query)) {
|
||||
$response->getBody()->write(json_encode(['data' => []]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
|
||||
$adultVideo = new \App\Models\AdultVideo($this->pdo);
|
||||
$actors = $adultVideo->searchActors($this->pdo, $query);
|
||||
|
||||
|
||||
$response->getBody()->write(json_encode(['data' => $actors]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display actors management page with duplicate detection
|
||||
*/
|
||||
public function actors(Request $request, Response $response, $args)
|
||||
{
|
||||
$actorModel = new \App\Models\Actor($this->pdo);
|
||||
|
||||
// Get query parameters
|
||||
$page = max(1, (int)($request->getQueryParams()['page'] ?? 1));
|
||||
$search = trim($request->getQueryParams()['search'] ?? '');
|
||||
$showDuplicates = $request->getQueryParams()['duplicates'] ?? false;
|
||||
$sort = trim($request->getQueryParams()['sort'] ?? 'name_asc');
|
||||
$perPage = 20;
|
||||
|
||||
$filters = [
|
||||
'search' => $search,
|
||||
'duplicates' => $showDuplicates,
|
||||
'sort' => $sort
|
||||
];
|
||||
|
||||
if ($showDuplicates) {
|
||||
// Get duplicate actors
|
||||
$actors = $this->getDuplicateActors($page, $perPage);
|
||||
$totalActors = $this->getDuplicateActorsCount();
|
||||
} else {
|
||||
// Get all actors with pagination
|
||||
$actors = $actorModel->getPaginated($this->pdo, $page, $perPage, $search, $sort);
|
||||
$totalActors = $actorModel->getTotalCount($this->pdo, $search);
|
||||
}
|
||||
|
||||
$totalPages = max(1, ceil($totalActors / $perPage));
|
||||
$currentPage = min($page, $totalPages);
|
||||
|
||||
return $this->render($response, 'admin/actors/index.twig', [
|
||||
'title' => 'Manage Actors',
|
||||
'actors' => $actors,
|
||||
'filters' => $filters,
|
||||
'pagination' => [
|
||||
'current' => $currentPage,
|
||||
'total' => $totalPages,
|
||||
'per_page' => $perPage,
|
||||
'total_items' => $totalActors,
|
||||
'from' => (($currentPage - 1) * $perPage) + 1,
|
||||
'to' => min($currentPage * $perPage, $totalActors)
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get duplicate actors grouped by name
|
||||
*/
|
||||
private function getDuplicateActors(int $page = 1, int $perPage = 20): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT
|
||||
LOWER(TRIM(name)) as normalized_name,
|
||||
COUNT(*) as duplicate_count,
|
||||
GROUP_CONCAT(id ORDER BY id) as actor_ids,
|
||||
GROUP_CONCAT(name ORDER BY id) as actor_names,
|
||||
GROUP_CONCAT(COALESCE(thumbnail_path, '') ORDER BY id) as thumbnails,
|
||||
GROUP_CONCAT(COALESCE(metadata, '{}') ORDER BY id) as metadata_list
|
||||
FROM actors
|
||||
GROUP BY LOWER(TRIM(name))
|
||||
HAVING COUNT(*) > 1
|
||||
ORDER BY duplicate_count DESC, normalized_name ASC
|
||||
LIMIT ? OFFSET ?
|
||||
");
|
||||
$stmt->execute([$perPage, $offset]);
|
||||
|
||||
$duplicateGroups = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$result = [];
|
||||
foreach ($duplicateGroups as $group) {
|
||||
$actorIds = explode(',', $group['actor_ids']);
|
||||
$actorNames = explode(',', $group['actor_names']);
|
||||
$thumbnails = explode(',', $group['thumbnails']);
|
||||
$metadataList = explode(',', $group['metadata_list']);
|
||||
|
||||
$actors = [];
|
||||
foreach ($actorIds as $index => $actorId) {
|
||||
$actors[] = [
|
||||
'id' => (int)$actorId,
|
||||
'name' => $actorNames[$index],
|
||||
'thumbnail_path' => $thumbnails[$index] ?: null,
|
||||
'metadata' => json_decode($metadataList[$index] ?: '{}', true),
|
||||
'stats' => $this->getActorStats((int)$actorId)
|
||||
];
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'normalized_name' => $group['normalized_name'],
|
||||
'duplicate_count' => (int)$group['duplicate_count'],
|
||||
'actors' => $actors
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total count of duplicate actor groups
|
||||
*/
|
||||
private function getDuplicateActorsCount(): int
|
||||
{
|
||||
$stmt = $this->pdo->query("
|
||||
SELECT COUNT(*) as count
|
||||
FROM (
|
||||
SELECT LOWER(TRIM(name)) as normalized_name
|
||||
FROM actors
|
||||
GROUP BY LOWER(TRIM(name))
|
||||
HAVING COUNT(*) > 1
|
||||
) as duplicates
|
||||
");
|
||||
return (int)$stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actor statistics
|
||||
*/
|
||||
private function getActorStats(int $actorId): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT
|
||||
COUNT(DISTINCT am.movie_id) as movie_count,
|
||||
COUNT(DISTINCT ats.tv_show_id) as tv_show_count,
|
||||
COUNT(DISTINCT aav.adult_video_id) as adult_video_count,
|
||||
COUNT(DISTINCT am.movie_id) + COUNT(DISTINCT ats.tv_show_id) + COUNT(DISTINCT aav.adult_video_id) as total_media_count
|
||||
FROM actors a
|
||||
LEFT JOIN actor_movie am ON a.id = am.actor_id
|
||||
LEFT JOIN actor_tv_show ats ON a.id = ats.actor_id
|
||||
LEFT JOIN actor_adult_video aav ON a.id = aav.actor_id
|
||||
WHERE a.id = ?
|
||||
");
|
||||
$stmt->execute([$actorId]);
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge duplicate actors
|
||||
*/
|
||||
public function mergeActors(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
$masterActorId = (int)($data['master_actor_id'] ?? 0);
|
||||
$duplicateActorIds = array_map('intval', $data['duplicate_actor_ids'] ?? []);
|
||||
|
||||
if (!$masterActorId || empty($duplicateActorIds)) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Master actor ID and duplicate actor IDs are required'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Verify master actor exists
|
||||
$masterActor = (new \App\Models\Actor($this->pdo))->find($masterActorId);
|
||||
if (!$masterActor) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Master actor not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
$mergedCount = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($duplicateActorIds as $duplicateId) {
|
||||
if ($duplicateId === $masterActorId) {
|
||||
continue; // Skip if trying to merge master with itself
|
||||
}
|
||||
|
||||
// Verify duplicate actor exists
|
||||
$duplicateActor = (new \App\Models\Actor($this->pdo))->find($duplicateId);
|
||||
if (!$duplicateActor) {
|
||||
$errors[] = "Actor ID {$duplicateId} not found";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move relationships from duplicate to master
|
||||
$this->moveActorRelationships($duplicateId, $masterActorId);
|
||||
|
||||
// Delete the duplicate actor
|
||||
$stmt = $this->pdo->prepare("DELETE FROM actors WHERE id = ?");
|
||||
$stmt->execute([$duplicateId]);
|
||||
|
||||
$mergedCount++;
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'message' => "Successfully merged {$mergedCount} duplicate actors",
|
||||
'merged_count' => $mergedCount,
|
||||
'errors' => $errors
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to merge actors: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all relationships from one actor to another
|
||||
*/
|
||||
private function moveActorRelationships(int $fromActorId, int $toActorId): void
|
||||
{
|
||||
// Move movie relationships
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE actor_movie
|
||||
SET actor_id = ?
|
||||
WHERE actor_id = ? AND movie_id NOT IN (
|
||||
SELECT movie_id FROM actor_movie WHERE actor_id = ?
|
||||
)
|
||||
");
|
||||
$stmt->execute([$toActorId, $fromActorId, $toActorId]);
|
||||
|
||||
// Remove duplicate movie relationships that may have been created
|
||||
$stmt = $this->pdo->prepare("
|
||||
DELETE FROM actor_movie
|
||||
WHERE actor_id = ? AND movie_id IN (
|
||||
SELECT movie_id FROM (
|
||||
SELECT movie_id FROM actor_movie WHERE actor_id = ? GROUP BY movie_id HAVING COUNT(*) > 1
|
||||
) as duplicates
|
||||
)
|
||||
");
|
||||
$stmt->execute([$fromActorId, $fromActorId]);
|
||||
|
||||
// Move TV show relationships
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE actor_tv_show
|
||||
SET actor_id = ?
|
||||
WHERE actor_id = ? AND tv_show_id NOT IN (
|
||||
SELECT tv_show_id FROM actor_tv_show WHERE actor_id = ?
|
||||
)
|
||||
");
|
||||
$stmt->execute([$toActorId, $fromActorId, $toActorId]);
|
||||
|
||||
// Remove duplicate TV show relationships
|
||||
$stmt = $this->pdo->prepare("
|
||||
DELETE FROM actor_tv_show
|
||||
WHERE actor_id = ? AND tv_show_id IN (
|
||||
SELECT tv_show_id FROM (
|
||||
SELECT tv_show_id FROM actor_tv_show WHERE actor_id = ? GROUP BY tv_show_id HAVING COUNT(*) > 1
|
||||
) as duplicates
|
||||
)
|
||||
");
|
||||
$stmt->execute([$fromActorId, $fromActorId]);
|
||||
|
||||
// Move adult video relationships
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE actor_adult_video
|
||||
SET actor_id = ?
|
||||
WHERE actor_id = ? AND adult_video_id NOT IN (
|
||||
SELECT adult_video_id FROM actor_adult_video WHERE actor_id = ?
|
||||
)
|
||||
");
|
||||
$stmt->execute([$toActorId, $fromActorId, $toActorId]);
|
||||
|
||||
// Remove duplicate adult video relationships
|
||||
$stmt = $this->pdo->prepare("
|
||||
DELETE FROM actor_adult_video
|
||||
WHERE actor_id = ? AND adult_video_id IN (
|
||||
SELECT adult_video_id FROM (
|
||||
SELECT adult_video_id FROM actor_adult_video WHERE actor_id = ? GROUP BY adult_video_id HAVING COUNT(*) > 1
|
||||
) as duplicates
|
||||
)
|
||||
");
|
||||
$stmt->execute([$fromActorId, $fromActorId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-merge duplicate actors (chooses master based on media count and thumbnail)
|
||||
*/
|
||||
public function autoMergeActors(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
$actorGroupIds = $data['actor_group_ids'] ?? [];
|
||||
|
||||
if (empty($actorGroupIds)) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'No actor groups specified for auto-merge'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
$totalMerged = 0;
|
||||
$groupsProcessed = 0;
|
||||
|
||||
foreach ($actorGroupIds as $groupId) {
|
||||
$actors = $this->getActorsByNormalizedName($groupId);
|
||||
if (count($actors) <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Choose master actor (prefer one with thumbnail, then most media associations)
|
||||
$masterActor = $this->chooseMasterActor($actors);
|
||||
$duplicateIds = array_filter(array_column($actors, 'id'), fn($id) => $id !== $masterActor['id']);
|
||||
|
||||
// Merge duplicates into master
|
||||
foreach ($duplicateIds as $duplicateId) {
|
||||
$this->moveActorRelationships($duplicateId, $masterActor['id']);
|
||||
|
||||
// Delete duplicate
|
||||
$stmt = $this->pdo->prepare("DELETE FROM actors WHERE id = ?");
|
||||
$stmt->execute([$duplicateId]);
|
||||
}
|
||||
|
||||
$totalMerged += count($duplicateIds);
|
||||
$groupsProcessed++;
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
|
||||
return $this->json($response, [
|
||||
'success' => true,
|
||||
'message' => "Auto-merged {$totalMerged} actors across {$groupsProcessed} groups",
|
||||
'merged_count' => $totalMerged,
|
||||
'groups_processed' => $groupsProcessed
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
'message' => 'Failed to auto-merge actors: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actors by normalized name
|
||||
*/
|
||||
private function getActorsByNormalizedName(string $normalizedName): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT id, name, thumbnail_path, metadata
|
||||
FROM actors
|
||||
WHERE LOWER(TRIM(name)) = ?
|
||||
ORDER BY id
|
||||
");
|
||||
$stmt->execute([$normalizedName]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the best master actor from a group
|
||||
*/
|
||||
private function chooseMasterActor(array $actors): array
|
||||
{
|
||||
// First, prefer actors with thumbnails
|
||||
$withThumbnails = array_filter($actors, fn($actor) => !empty($actor['thumbnail_path']));
|
||||
if (!empty($withThumbnails)) {
|
||||
$actors = $withThumbnails;
|
||||
}
|
||||
|
||||
// Then prefer the one with most media associations
|
||||
$maxMediaCount = 0;
|
||||
$masterActor = $actors[0];
|
||||
|
||||
foreach ($actors as $actor) {
|
||||
$stats = $this->getActorStats($actor['id']);
|
||||
$mediaCount = $stats['total_media_count'];
|
||||
|
||||
if ($mediaCount > $maxMediaCount) {
|
||||
$maxMediaCount = $mediaCount;
|
||||
$masterActor = $actor;
|
||||
}
|
||||
}
|
||||
|
||||
return $masterActor;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user