i dont know

This commit is contained in:
Lars Behrends
2025-10-20 23:40:55 +02:00
parent 552bb72370
commit 73d8441787
33 changed files with 3079 additions and 69 deletions

View File

@@ -78,6 +78,28 @@ class ActorController extends Controller
$stmt->execute(['actor_id' => $actorId]);
$tvShows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($scenes as &$scene) {
if (!empty($scene['metadata'])) {
$metadata = json_decode($scene['metadata'], true);
// Use local cover path if available, otherwise fall back to original URL
if (!empty($metadata['local_cover_path'])) {
$scene['poster_url'] = $metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$scene['poster_url'] = $metadata['cover_url'];
}
// Add actors data if available
if (!empty($metadata['actors'])) {
$scene['actors'] = $metadata['actors'];
}
}
}
return $this->view->render($response, 'actor/show.twig', [
'title' => $actor['name'],
'actor' => $actor,
@@ -86,7 +108,6 @@ class ActorController extends Controller
'tv_shows' => $tvShows
]);
}
public function index(Request $request, Response $response, $args)
{
// Get all actors with their media counts from all types

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;
class AdminBaseController
{
protected Twig $view;
protected \PDO $pdo;
public function __construct(\PDO $pdo, Twig $view)
{
$this->pdo = $pdo;
$this->view = $view;
}
/**
* Render a template
*/
protected function render(Response $response, string $template, array $data = []): Response
{
// Add common admin data
$data['auth'] = [
'check' => isset($_SESSION['user_id']),
'user' => [
'username' => $_SESSION['username'] ?? 'Admin',
'is_admin' => $_SESSION['is_admin'] ?? false
]
];
// Add current route for active menu highlighting
$route = $this->getCurrentRoute();
if ($route) {
$data['current_route'] = $route;
}
return $this->view->render($response, $template, $data);
}
/**
* Get current route name
*/
protected function getCurrentRoute(): ?string
{
$route = $_SERVER['REQUEST_URI'] ?? '/';
$basePath = '/admin/';
if (strpos($route, $basePath) === 0) {
$route = substr($route, strlen($basePath));
$parts = explode('/', $route);
return $parts[0] ?: 'index';
}
return null;
}
/**
* Return JSON response
*/
protected function json(Response $response, $data, int $status = 200): Response
{
$response->getBody()->write(json_encode($data));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus($status);
}
}

View File

@@ -15,13 +15,13 @@ use App\Services\ExophaseSyncService;
use PDO;
use Slim\Views\Twig;
class AdminController extends Controller
class AdminController extends AdminBaseController
{
private PDO $pdo;
protected PDO $pdo;
public function __construct(PDO $pdo, Twig $view)
{
parent::__construct($view);
parent::__construct($pdo, $view);
$this->pdo = $pdo;
}
@@ -33,7 +33,7 @@ class AdminController extends Controller
$syncLogModel = new SyncLog($this->pdo);
$recentSyncs = SyncLog::getRecent($this->pdo, 10);
return $this->view->render($response, 'admin/index.twig', [
return $this->render($response, 'admin/index.twig', [
'title' => 'Admin Dashboard',
'sources' => $sources,
'recent_syncs' => $recentSyncs
@@ -119,14 +119,23 @@ class AdminController extends Controller
return min(100, round(($processed / $total) * 100, 2));
}
public function settings(Request $request, Response $response, $args)
{
return $this->render($response, 'admin/settings.twig', [
'title' => 'Admin Settings',
'current_route' => 'settings'
]);
}
public function sources(Request $request, Response $response, $args)
{
$sourceModel = new Source($this->pdo);
$sources = $sourceModel->findAll();
return $this->view->render($response, 'admin/sources.twig', [
return $this->render($response, 'admin/sources.twig', [
'title' => 'Source Management',
'sources' => $sources
'sources' => $sources,
'current_route' => 'sources'
]);
}

View File

@@ -41,7 +41,7 @@ class AdultController extends Controller
// Use local cover path if available, otherwise fall back to original URL
if (!empty($metadata['local_cover_path'])) {
$video['poster_url'] = '/public/images/'.$metadata['local_cover_path'];
$video['poster_url'] = $metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$video['poster_url'] = $metadata['cover_url'];
}
@@ -103,13 +103,13 @@ class AdultController extends Controller
// Add local image paths and other metadata to the video data for template compatibility
if (!empty($metadata['local_cover_path'])) {
$adultVideo['poster_url'] = '/public/images/'.$metadata['local_cover_path'];
$adultVideo['poster_url'] = '/images/'.$metadata['local_cover_path'];
} elseif (!empty($metadata['cover_url'])) {
$adultVideo['poster_url'] = $metadata['cover_url'];
}
if (!empty($metadata['local_screenshot_path'])) {
$adultVideo['screenshot_url'] = '/public/images/'.$metadata['local_screenshot_path'];
$adultVideo['screenshot_url'] = '/images/'.$metadata['local_screenshot_path'];
}
// Add actors data if available

View File

@@ -7,22 +7,22 @@ use Psr\Http\Message\ServerRequestInterface as Request;
use App\Models\Game;
use App\Models\Movie;
use App\Models\TvShow;
use App\Models\MusicArtist;
use App\Models\AdultVideo;
use App\Models\SyncLog;
use Slim\Views\Twig;
class DashboardController extends Controller
{
public function __construct(Twig $view)
private \PDO $pdo;
public function __construct(\PDO $pdo, Twig $view)
{
parent::__construct($view);
$this->pdo = $pdo;
}
public function index(Request $request, Response $response, $args)
{
$pdo = $this->view->getEnvironment()->getGlobals()['pdo'] ?? null;
if (!$pdo) {
if (!$this->pdo) {
return $this->view->render($response, 'dashboard/index.twig', [
'title' => 'Dashboard',
'stats' => [
@@ -38,22 +38,24 @@ class DashboardController extends Controller
}
// Get statistics from models
$gameStats = Game::getStats($pdo);
$movieStats = Movie::getStats($pdo);
$tvShowStats = TvShow::getStats($pdo);
$musicStats = MusicArtist::getStats($pdo);
$syncStats = SyncLog::getStats($pdo);
$gameStats = Game::getStats($this->pdo);
$movieStats = Movie::getStats($this->pdo);
$tvShowStats = TvShow::getStats($this->pdo);
$adultStats = AdultVideo::getStats($this->pdo);
// $musicStats = MusicArtist::getStats($this->pdo);
//$syncStats = SyncLog::getStats($this->pdo);
// Get recent activity
$recentGames = Game::getRecent($pdo, 5);
$recentMovies = Movie::getRecent($pdo, 5);
$recentSyncs = SyncLog::getRecent($pdo, 5);
$recentGames = Game::getRecent($this->pdo, 5);
$recentMovies = Movie::getRecent($this->pdo, 5);
$recentSyncs = SyncLog::getRecent($this->pdo, 5);
// Calculate total media count
$totalMedia = ($gameStats['total_games'] ?? 0) +
($movieStats['total_movies'] ?? 0) +
($tvShowStats['total_shows'] ?? 0) +
($musicStats['total_artists'] ?? 0);
($musicStats['total_artists'] ?? 0)+
($adultStats['total_adult_videos'] ?? 0);
$stats = [
'total_media' => $totalMedia,
@@ -63,11 +65,13 @@ class DashboardController extends Controller
'total_episodes' => $tvShowStats['total_episodes'] ?? 0,
'total_music' => $musicStats['total_artists'] ?? 0,
'total_playtime' => $gameStats['total_playtime'] ?? 0,
'total_adult_videos' => $adultStats['total_adult_videos'] ?? 0,
'watched_movies' => $movieStats['watched_movies'] ?? 0,
'favorite_games' => $gameStats['favorite_games'] ?? 0,
'favorite_movies' => $movieStats['favorite_movies'] ?? 0,
'favorite_shows' => $tvShowStats['favorite_shows'] ?? 0,
'favorite_music' => $musicStats['favorite_artists'] ?? 0,
'favorite_adult_videos' => $adultStats['favorite_adult_videos'] ?? 0,
];
return $this->view->render($response, 'dashboard/index.twig', [

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class ImageController extends Controller
{
/**
* Serve an image from internal storage
* @param Request $request
* @param Response $response
* @param array $args
* @return Response
*/
public function serve(Request $request, Response $response, $args): Response
{
$imagePath = $args['path'] ?? '';
// Security: Prevent directory traversal
$imagePath = str_replace(['../', '..\\'], '', $imagePath);
$fullPath = __DIR__ . '/../../storage/images/' . $imagePath;
// Check if file exists
if (!file_exists($fullPath)) {
return $response->withStatus(404, 'Image not found');
}
// Get file extension and set appropriate content type
$extension = strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));
$contentTypes = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
'svg' => 'image/svg+xml',
];
$contentType = $contentTypes[$extension] ?? 'application/octet-stream';
// Read and serve the file
$fileContent = file_get_contents($fullPath);
$response = $response->withHeader('Content-Type', $contentType);
$response = $response->withHeader('Cache-Control', 'public, max-age=3600'); // Cache for 1 hour
$response->getBody()->write($fileContent);
return $response;
}
}

View File

@@ -0,0 +1,219 @@
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use App\Models\Source;
use App\Models\SyncLog;
class MediaSourceController extends AdminBaseController
{
private $source;
private $syncLog;
public function __construct(\PDO $pdo, \Slim\Views\Twig $view)
{
parent::__construct($pdo, $view);
$this->source = new Source($pdo);
$this->syncLog = new SyncLog($pdo);
}
// List all sources
public function index(Request $request, Response $response, array $args): Response
{
$sources = $this->source->all();
return $this->render($response, 'admin/sources.twig', [
'sources' => $sources,
'current_route' => 'sources'
]);
}
// Show create form
public function create(Request $request, Response $response, array $args): Response
{
return $this->render($response, 'admin/sources/create.twig', [
'current_route' => 'sources'
]);
}
// Store new source
public function store(Request $request, Response $response, array $args): Response
{
$data = $request->getParsedBody();
// Basic validation
if (empty($data['name']) || empty($data['type']) || empty($data['path'])) {
$this->flash->addMessage('error', 'Name, type, and path are required');
return $response->withHeader('Location', '/admin/sources/create')->withStatus(302);
}
try {
$sourceData = [
'name' => $data['name'],
'type' => $data['type'],
'path' => $data['path'],
'username' => $data['username'] ?? null,
'password' => !empty($data['password']) ? password_hash($data['password'], PASSWORD_DEFAULT) : null,
'is_active' => isset($data['is_active']) ? 1 : 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
$this->source->create($sourceData);
$this->flash->addMessage('success', 'Source created successfully');
return $response->withHeader('Location', '/admin/sources')->withStatus(302);
} catch (\Exception $e) {
$this->flash->addMessage('error', 'Error creating source: ' . $e->getMessage());
return $response->withHeader('Location', '/admin/sources/create')->withStatus(302);
}
}
// Show edit form
public function edit(Request $request, Response $response, array $args): Response
{
$id = $args['id'];
$source = $this->source->find($id);
if (!$source) {
$this->flash->addMessage('error', 'Source not found');
return $response->withHeader('Location', '/admin/sources')->withStatus(302);
}
return $this->render($response, 'admin/sources/edit.twig', [
'source' => $source,
'current_route' => 'sources'
]);
}
// Update source
public function update(Request $request, Response $response, array $args): Response
{
$id = $args['id'];
$data = $request->getParsedBody();
try {
$sourceData = [
'name' => $data['name'],
'type' => $data['type'],
'path' => $data['path'],
'username' => $data['username'] ?? null,
'is_active' => isset($data['is_active']) ? 1 : 0,
'updated_at' => date('Y-m-d H:i:s')
];
// Only update password if provided
if (!empty($data['password'])) {
$sourceData['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
}
$this->source->update($id, $sourceData);
$this->flash->addMessage('success', 'Source updated successfully');
return $response->withHeader('Location', '/admin/sources')->withStatus(302);
} catch (\Exception $e) {
$this->flash->addMessage('error', 'Error updating source: ' . $e->getMessage());
return $response->withHeader('Location', '/admin/sources/' . $id . '/edit')->withStatus(302);
}
}
// Delete source
public function destroy(Request $request, Response $response, array $args): Response
{
$id = $args['id'];
try {
$this->source->delete($id);
$this->flash->addMessage('success', 'Source deleted successfully');
} catch (\Exception $e) {
$this->flash->addMessage('error', 'Error deleting source: ' . $e->getMessage());
}
return $response->withHeader('Location', '/admin/sources')->withStatus(302);
}
// Start sync for a source
public function startSync(Request $request, Response $response, array $args): Response
{
$sourceId = $args['id'];
$source = $this->source->find($sourceId);
if (!$source) {
return $this->json($response, [
'success' => false,
'message' => 'Source not found'
], 404);
}
try {
// Create a sync log entry
$logId = $this->syncLog->create([
'source_id' => $sourceId,
'type' => 'full',
'status' => 'pending',
'started_at' => date('Y-m-d H:i:s'),
'created_at' => date('Y-m-d H:i:s')
]);
// Start sync in background (you'll need to implement this)
$this->startBackgroundSync($sourceId, $logId);
return $this->json($response, [
'success' => true,
'message' => 'Sync started',
'log_id' => $logId
]);
} catch (\Exception $e) {
return $this->json($response, [
'success' => false,
'message' => 'Error starting sync: ' . $e->getMessage()
], 500);
}
}
// Get sync status
public function syncStatus(Request $request, Response $response, array $args): Response
{
$logId = $args['log_id'] ?? null;
if (!$logId) {
return $this->json($response, [
'success' => false,
'message' => 'Log ID is required'
], 400);
}
$log = $this->syncLog->find($logId);
if (!$log) {
return $this->json($response, [
'success' => false,
'message' => 'Sync log not found'
], 404);
}
return $this->json($response, [
'success' => true,
'status' => $log['status'],
'progress' => $log['progress'] ?? 0,
'message' => $log['message'] ?? ''
]);
}
// Start background sync process
private function startBackgroundSync($sourceId, $logId)
{
// This is a simplified example - you'll need to implement this based on your needs
$command = sprintf(
'php %s/console.php sync:source %d --log=%d > /dev/null 2>&1 &',
dirname(__DIR__, 3),
$sourceId,
$logId
);
exec($command);
}
}

View File

@@ -0,0 +1,158 @@
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use PDO;
use Slim\Views\Twig;
class SettingsController extends Controller
{
private PDO $pdo;
public function __construct(PDO $pdo, Twig $view)
{
parent::__construct($view);
$this->pdo = $pdo;
}
public function index(Request $request, Response $response, $args)
{
$settings = $this->getSettings();
$sources = $this->getSources();
return $this->view->render($response, 'admin/settings.twig', [
'title' => 'Admin Settings',
'settings' => $settings,
'sources' => $sources
]);
}
public function save(Request $request, Response $response, $args)
{
$data = $request->getParsedBody();
// Save general settings
$this->saveGeneralSettings($data);
// Save source-specific settings
if (isset($data['sources']) && is_array($data['sources'])) {
$this->saveSourceSettings($data['sources']);
}
return $this->view->render($response, 'admin/settings.twig', [
'title' => 'Admin Settings',
'settings' => $this->getSettings(),
'sources' => $this->getSources(),
'success' => 'Settings saved successfully!'
]);
}
private function getSettings(): array
{
// Get general application settings
$settings = [];
// Get media type visibility settings
$stmt = $this->pdo->prepare("SELECT setting_key, setting_value FROM settings WHERE setting_key LIKE 'media_visibility_%'");
$stmt->execute();
$mediaVisibilitySettings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$settings['media_visibility'] = [
'games' => $mediaVisibilitySettings['media_visibility_games'] ?? 'authenticated', // Default: authenticated users only
'movies' => $mediaVisibilitySettings['media_visibility_movies'] ?? 'authenticated',
'tvshows' => $mediaVisibilitySettings['media_visibility_tvshows'] ?? 'authenticated',
'music' => $mediaVisibilitySettings['media_visibility_music'] ?? 'authenticated',
'adult' => $mediaVisibilitySettings['media_visibility_adult'] ?? 'authenticated', // Adult content requires auth by default
'actors' => $mediaVisibilitySettings['media_visibility_actors'] ?? 'authenticated'
];
// You can extend this to include more settings like:
// - Sync intervals
// - Default sync types
// - Notification preferences
// - Theme settings
// - etc.
return $settings;
}
private function getSources(): array
{
$stmt = $this->pdo->prepare("SELECT * FROM sources ORDER BY display_name");
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
private function saveGeneralSettings(array $data): void
{
// Save general settings to a settings table or config file
// For now, we'll store them in a simple settings table
foreach ($data as $key => $value) {
if (strpos($key, 'setting_') === 0) {
$settingKey = substr($key, 8); // Remove 'setting_' prefix
// Check if setting exists
$stmt = $this->pdo->prepare("SELECT id FROM settings WHERE setting_key = :key LIMIT 1");
$stmt->execute(['key' => $settingKey]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing) {
// Update existing setting
$stmt = $this->pdo->prepare("UPDATE settings SET setting_value = :value WHERE setting_key = :key");
$stmt->execute(['key' => $settingKey, 'value' => $value]);
} else {
// Insert new setting
$stmt = $this->pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (:key, :value)");
$stmt->execute(['key' => $settingKey, 'value' => $value]);
}
}
}
// Save media visibility settings
if (isset($data['media_visibility']) && is_array($data['media_visibility'])) {
foreach ($data['media_visibility'] as $mediaType => $visibility) {
$settingKey = "media_visibility_{$mediaType}";
// Check if setting exists
$stmt = $this->pdo->prepare("SELECT id FROM settings WHERE setting_key = :key LIMIT 1");
$stmt->execute(['key' => $settingKey]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing) {
// Update existing setting
$stmt = $this->pdo->prepare("UPDATE settings SET setting_value = :value WHERE setting_key = :key");
$stmt->execute(['key' => $settingKey, 'value' => $visibility]);
} else {
// Insert new setting
$stmt = $this->pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (:key, :value)");
$stmt->execute(['key' => $settingKey, 'value' => $visibility]);
}
}
}
}
private function saveSourceSettings(array $sources): void
{
foreach ($sources as $sourceId => $sourceData) {
// Update source configuration
$config = isset($sourceData['config']) ? json_encode($sourceData['config']) : '{}';
$stmt = $this->pdo->prepare("
UPDATE sources
SET api_url = :api_url, api_key = :api_key, config = :config, is_active = :is_active
WHERE id = :id
");
$stmt->execute([
'id' => $sourceId,
'api_url' => $sourceData['api_url'] ?? '',
'api_key' => $sourceData['api_key'] ?? '',
'config' => $config,
'is_active' => isset($sourceData['is_active']) ? 1 : 0
]);
}
}
}

View File

@@ -0,0 +1,294 @@
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use App\Models\Source;
use App\Models\SyncLog;
use App\Services\JellyfinSyncService;
use App\Services\LocalSyncService;
use App\Services\SambaSyncService;
use App\Services\NfsSyncService;
class SyncController extends AdminBaseController
{
private $source;
private $syncLog;
private $syncServices = [];
public function __construct(\PDO $pdo, \Slim\Views\Twig $view)
{
parent::__construct($pdo, $view);
$this->source = new Source($pdo);
$this->syncLog = new SyncLog($pdo);
// Initialize sync services
$this->syncServices = [
'jellyfin' => new JellyfinSyncService($pdo),
'local' => new LocalSyncService($pdo),
'samba' => new SambaSyncService($pdo),
'nfs' => new NfsSyncService($pdo)
];
}
// Show sync dashboard
public function index(Request $request, Response $response, array $args): Response
{
// Get recent sync logs
$recentLogs = $this->syncLog->orderBy('created_at', 'desc')->limit(10)->get();
// Get sync statistics
$stats = [
'total_syncs' => $this->syncLog->count(),
'successful_syncs' => $this->syncLog->where('status', 'completed')->count(),
'failed_syncs' => $this->syncLog->where('status', 'failed')->count(),
'pending_syncs' => $this->syncLog->where('status', 'pending')->count(),
];
return $this->render($response, 'admin/sync/index.twig', [
'recent_logs' => $recentLogs,
'stats' => $stats,
'current_route' => 'sync'
]);
}
// Start a new sync
public function start(Request $request, Response $response, array $args): Response
{
$data = $request->getParsedBody();
$type = $data['type'] ?? 'full'; // full, scan, update
$sourceId = $data['source_id'] ?? null;
try {
// Create a new sync log
$logData = [
'source_id' => $sourceId,
'sync_type' => $type,
'status' => 'pending',
'started_at' => date('Y-m-d H:i:s'),
'created_at' => date('Y-m-d H:i:s')
];
$logId = $this->syncLog->create($logData);
// Start sync in background
$this->startBackgroundSync($sourceId, $logId, $type);
return $this->json($response, [
'success' => true,
'message' => 'Sync started',
'log_id' => $logId
]);
} catch (\Exception $e) {
return $this->json($response, [
'success' => false,
'message' => 'Error starting sync: ' . $e->getMessage()
], 500);
}
}
// Get sync status
public function status(Request $request, Response $response, array $args): Response
{
$logId = $args['log_id'] ?? null;
if (!$logId) {
return $this->json($response, [
'success' => false,
'message' => 'Log ID is required'
], 400);
}
$log = $this->syncLog->find($logId);
if (!$log) {
return $this->json($response, [
'success' => false,
'message' => 'Sync log not found'
], 404);
}
// Get detailed status from the sync service if available
$status = [
'status' => $log['status'],
'progress' => (int)($log['progress'] ?? 0),
'message' => $log['message'] ?? '',
'started_at' => $log['started_at'],
'completed_at' => $log['completed_at'] ?? null,
'total_items' => (int)($log['total_items'] ?? 0),
'processed_items' => (int)($log['processed_items'] ?? 0),
'new_items' => (int)($log['new_items'] ?? 0),
'updated_items' => (int)($log['updated_items'] ?? 0),
'deleted_items' => (int)($log['deleted_items'] ?? 0)
];
return $this->json($response, [
'success' => true,
'log' => $status
]);
}
// Cancel a running sync
public function cancel(Request $request, Response $response, array $args): Response
{
$logId = $args['log_id'] ?? null;
if (!$logId) {
return $this->json($response, [
'success' => false,
'message' => 'Log ID is required'
], 400);
}
try {
// Update the log status to cancelled
$this->syncLog->update($logId, [
'status' => 'cancelled',
'completed_at' => date('Y-m-d H:i:s'),
'message' => 'Sync cancelled by user'
]);
// TODO: Send a signal to the running process to cancel
return $this->json($response, [
'success' => true,
'message' => 'Sync cancelled'
]);
} catch (\Exception $e) {
return $this->json($response, [
'success' => false,
'message' => 'Error cancelling sync: ' . $e->getMessage()
], 500);
}
}
// Clear sync logs
public function clearLogs(Request $request, Response $response, array $args): Response
{
$type = $request->getParsedBody()['type'] ?? 'completed'; // completed, all
try {
if ($type === 'all') {
$this->syncLog->query('TRUNCATE TABLE sync_logs');
} else {
$this->syncLog->where('status', 'completed')->delete();
$this->syncLog->where('status', 'failed')->delete();
$this->syncLog->where('status', 'cancelled')->delete();
}
return $this->json($response, [
'success' => true,
'message' => 'Logs cleared successfully'
]);
} catch (\Exception $e) {
return $this->json($response, [
'success' => false,
'message' => 'Error clearing logs: ' . $e->getMessage()
], 500);
}
}
// Start background sync process
private function startBackgroundSync($sourceId, $logId, $type = 'full')
{
// This is a simplified example - you'll need to implement this based on your needs
$command = sprintf(
'php %s/console.php sync:start --log=%d --type=%s %s > /dev/null 2>&1 &',
dirname(__DIR__, 3), // Path to your project root
$logId,
escapeshellarg($type),
$sourceId ? '--source=' . $sourceId : ''
);
exec($command);
}
// Process sync (called from CLI)
public function processSync($sourceId, $logId, $type = 'full')
{
try {
$log = $this->syncLog->find($logId);
if (!$log) {
throw new \Exception('Sync log not found');
}
// Update log status to started
$this->syncLog->update($logId, [
'status' => 'in_progress',
'started_at' => date('Y-m-d H:i:s'),
'message' => 'Sync started'
]);
$source = null;
if ($sourceId) {
$source = $this->source->find($sourceId);
if (!$source) {
throw new \Exception('Source not found');
}
}
// Get the appropriate sync service
$service = $this->getSyncService($source ? $source['type'] : 'local');
// Start sync
$result = $service->sync($source, $type, function($progress, $message) use ($logId) {
// Update progress callback
$this->updateSyncProgress($logId, $progress, $message);
});
// Update log with final status
$this->syncLog->update($logId, [
'status' => $result['success'] ? 'completed' : 'failed',
'completed_at' => date('Y-m-d H:i:s'),
'message' => $result['message'] ?? 'Sync completed',
'total_items' => $result['total_items'] ?? 0,
'processed_items' => $result['processed_items'] ?? 0,
'new_items' => $result['new_items'] ?? 0,
'updated_items' => $result['updated_items'] ?? 0,
'deleted_items' => $result['deleted_items'] ?? 0,
'errors' => !empty($result['errors']) ? json_encode($result['errors']) : null
]);
return $result;
} catch (\Exception $e) {
// Update log with error
if (isset($logId) && $this->syncLog) {
$this->syncLog->update($logId, [
'status' => 'failed',
'completed_at' => date('Y-m-d H:i:s'),
'message' => 'Error: ' . $e->getMessage()
]);
}
throw $e;
}
}
// Update sync progress
private function updateSyncProgress($logId, $progress, $message = '')
{
$this->syncLog->update($logId, [
'progress' => $progress,
'message' => $message,
'updated_at' => date('Y-m-d H:i:s')
]);
}
// Get the appropriate sync service
private function getSyncService($type)
{
$type = strtolower($type);
if (!isset($this->syncServices[$type])) {
throw new \Exception("Unsupported source type: $type");
}
return $this->syncServices[$type];
}
}

View File

@@ -41,7 +41,11 @@ class TvShowController extends Controller
$totalPages = ceil($totalCount / $perPage);
$hasNextPage = $page < $totalPages;
$hasPrevPage = $page > 1;
/*
echo '<pre>';
print_r($tvshows);
die();
*/
return $this->view->render($response, 'tvshows/index.twig', [
'title' => 'TV Shows',
'tvshows' => $tvshows,
@@ -107,4 +111,67 @@ class TvShowController extends Controller
'seasons' => $seasons
]);
}
public function delete(Request $request, Response $response, $args)
{
$tvShowId = (int) $args['id'];
// Get TV show details to access metadata
$stmt = $this->pdo->prepare("
SELECT t.*
FROM tv_shows t
WHERE t.id = :id
");
$stmt->execute(['id' => $tvShowId]);
$tvShow = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$tvShow) {
return $response->withStatus(404);
}
// Decode metadata to find image paths
$metadata = json_decode($tvShow['metadata'], true);
// Delete associated images
$imagesDeleted = [];
if (!empty($metadata['local_poster_path'])) {
$posterPath = __DIR__ . '/../storage/images/' . $metadata['local_poster_path'];
if (file_exists($posterPath)) {
unlink($posterPath);
$imagesDeleted[] = $metadata['local_poster_path'];
}
}
if (!empty($metadata['local_backdrop_path'])) {
$backdropPath = __DIR__ . '/../storage/images/' . $metadata['local_backdrop_path'];
if (file_exists($backdropPath)) {
unlink($backdropPath);
$imagesDeleted[] = $metadata['local_backdrop_path'];
}
}
// Delete actor relationships
$stmt = $this->pdo->prepare("
DELETE FROM actor_tv_show
WHERE tv_show_id = :tv_show_id
");
$stmt->execute(['tv_show_id' => $tvShowId]);
// Delete the TV show record
$stmt = $this->pdo->prepare("DELETE FROM tv_shows WHERE id = :id");
$result = $stmt->execute(['id' => $tvShowId]);
if ($result) {
return $response->withJson([
'success' => true,
'message' => 'TV show deleted successfully',
'images_deleted' => $imagesDeleted
]);
} else {
return $response->withJson([
'success' => false,
'message' => 'Failed to delete TV show'
], 500);
}
}
}