mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
first commit
This commit is contained in:
109
app/Models/AdultVideo.php
Normal file
109
app/Models/AdultVideo.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class AdultVideo extends Model
|
||||
{
|
||||
protected string $table = 'adult_videos';
|
||||
protected array $fillable = [
|
||||
'title',
|
||||
'overview',
|
||||
'poster_url',
|
||||
'backdrop_url',
|
||||
'rating',
|
||||
'runtime_minutes',
|
||||
'release_date',
|
||||
'director',
|
||||
'writer',
|
||||
'cast',
|
||||
'genre',
|
||||
'metadata',
|
||||
'watched',
|
||||
'watch_count',
|
||||
'is_favorite',
|
||||
'source_id',
|
||||
'external_id'
|
||||
];
|
||||
|
||||
public static function getAllWithPagination(\PDO $pdo, int $page, int $perPage, string $search = ''): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$whereClause = '';
|
||||
$params = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$whereClause = "WHERE (title LIKE :search OR overview LIKE :search)";
|
||||
$params['search'] = "%{$search}%";
|
||||
}
|
||||
|
||||
$sql = "
|
||||
SELECT av.*, s.display_name as source_name
|
||||
FROM adult_videos av
|
||||
JOIN sources s ON av.source_id = s.id
|
||||
{$whereClause}
|
||||
ORDER BY av.created_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
|
||||
|
||||
if (!empty($search)) {
|
||||
$stmt->bindValue(':search', "%{$search}%", \PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public static function getTotalCount(\PDO $pdo, string $search = ''): int
|
||||
{
|
||||
$whereClause = '';
|
||||
$params = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$whereClause = "WHERE (title LIKE :search OR overview LIKE :search)";
|
||||
$params['search'] = "%{$search}%";
|
||||
}
|
||||
|
||||
$sql = "SELECT COUNT(*) as count FROM adult_videos {$whereClause}";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
if (!empty($search)) {
|
||||
$stmt->bindValue(':search', "%{$search}%", \PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
return (int) $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
|
||||
}
|
||||
|
||||
public function markAsWatched(): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare("UPDATE adult_videos SET watched = 1, watch_count = watch_count + 1, updated_at = NOW() WHERE id = :id");
|
||||
return $stmt->execute(['id' => $this->id]);
|
||||
}
|
||||
|
||||
public function markAsUnwatched(): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare("UPDATE adult_videos SET watched = 0, updated_at = NOW() WHERE id = :id");
|
||||
return $stmt->execute(['id' => $this->id]);
|
||||
}
|
||||
|
||||
public function toggleFavorite(): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare("UPDATE adult_videos SET is_favorite = !is_favorite, updated_at = NOW() WHERE id = :id");
|
||||
return $stmt->execute(['id' => $this->id]);
|
||||
}
|
||||
|
||||
public function source(): ?Source
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM sources WHERE id = :source_id");
|
||||
$stmt->execute(['source_id' => $this->source_id]);
|
||||
$sourceData = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
return $sourceData ? new Source($this->pdo, $sourceData) : null;
|
||||
}
|
||||
}
|
||||
298
app/Models/Game.php
Normal file
298
app/Models/Game.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Game extends Model
|
||||
{
|
||||
protected string $table = 'games';
|
||||
protected array $fillable = [
|
||||
'title',
|
||||
'game_key',
|
||||
'description',
|
||||
'genre',
|
||||
'developer',
|
||||
'publisher',
|
||||
'release_date',
|
||||
'platform',
|
||||
'platform_game_id',
|
||||
'steam_app_id',
|
||||
'image_url',
|
||||
'banner_url',
|
||||
'rating',
|
||||
'playtime_minutes',
|
||||
'completion_percentage',
|
||||
'is_installed',
|
||||
'is_favorite',
|
||||
'metadata',
|
||||
'platform_achievements',
|
||||
'platform_stats',
|
||||
'source_id',
|
||||
'last_played_at'
|
||||
];
|
||||
|
||||
protected array $casts = [
|
||||
'rating' => 'float',
|
||||
'playtime_minutes' => 'int',
|
||||
'completion_percentage' => 'int',
|
||||
'is_installed' => 'bool',
|
||||
'is_favorite' => 'bool',
|
||||
'release_date' => 'date',
|
||||
'last_played_at' => 'datetime',
|
||||
'platform_achievements' => 'array',
|
||||
'platform_stats' => 'array'
|
||||
];
|
||||
|
||||
public function source()
|
||||
{
|
||||
return new Source($this->pdo);
|
||||
}
|
||||
|
||||
public function markAsPlayed(int $minutes = 60): bool
|
||||
{
|
||||
$this->playtime_minutes += $minutes;
|
||||
$this->last_played_at = date('Y-m-d H:i:s');
|
||||
return $this->update($this->id, [
|
||||
'playtime_minutes' => $this->playtime_minutes,
|
||||
'last_played_at' => $this->last_played_at
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleFavorite(): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'is_favorite' => !$this->is_favorite
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleInstalled(): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'is_installed' => !$this->is_installed
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateCompletion(int $percentage): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'completion_percentage' => min(100, max(0, $percentage))
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateRating(float $rating): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'rating' => min(10.0, max(0.0, $rating))
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getStats(\PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
COUNT(*) as total_games,
|
||||
SUM(playtime_minutes) as total_playtime,
|
||||
AVG(rating) as avg_rating,
|
||||
COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_games,
|
||||
COUNT(CASE WHEN is_installed = 1 THEN 1 END) as installed_games,
|
||||
AVG(completion_percentage) as avg_completion
|
||||
FROM games
|
||||
");
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public static function getRecent(\PDO $pdo, int $limit = 10): array
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT g.*, s.display_name as source_name
|
||||
FROM games g
|
||||
JOIN sources s ON g.source_id = s.id
|
||||
WHERE g.last_played_at IS NOT NULL
|
||||
ORDER BY g.last_played_at DESC
|
||||
LIMIT :limit
|
||||
");
|
||||
$stmt->execute(['limit' => $limit]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a game key for grouping games across platforms
|
||||
*/
|
||||
public static function generateGameKey(string $title, string $platform = null): string
|
||||
{
|
||||
// Normalize title for consistent grouping
|
||||
$normalized = strtolower(trim($title));
|
||||
$normalized = preg_replace('/[^\w\s]/', '', $normalized); // Remove special characters
|
||||
$normalized = preg_replace('/\s+/', ' ', $normalized); // Normalize whitespace
|
||||
$normalized = trim($normalized);
|
||||
|
||||
// Add platform to make it unique if provided
|
||||
if ($platform) {
|
||||
$normalized .= ' ' . strtolower($platform);
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all platform versions of a game
|
||||
*/
|
||||
public function getPlatformVersions(): array
|
||||
{
|
||||
if (!$this->game_key) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT g.*, s.display_name as source_name
|
||||
FROM games g
|
||||
JOIN sources s ON g.source_id = s.id
|
||||
WHERE g.game_key = :game_key
|
||||
ORDER BY g.platform, g.source_id
|
||||
");
|
||||
$stmt->execute(['game_key' => $this->game_key]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get games grouped by title for display
|
||||
*/
|
||||
public static function getGroupedGames(\PDO $pdo, int $limit = 50): array
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
game_key,
|
||||
title,
|
||||
COUNT(*) as platform_count,
|
||||
GROUP_CONCAT(DISTINCT platform ORDER BY platform) as platforms,
|
||||
GROUP_CONCAT(DISTINCT source_id ORDER BY source_id) as source_ids,
|
||||
MAX(image_url) as image_url,
|
||||
MAX(last_played_at) as last_played_at,
|
||||
SUM(playtime_minutes) as total_playtime,
|
||||
MAX(completion_percentage) as max_completion,
|
||||
GROUP_CONCAT(DISTINCT genre ORDER BY genre) as genres
|
||||
FROM games
|
||||
WHERE game_key IS NOT NULL
|
||||
GROUP BY game_key, title
|
||||
ORDER BY last_played_at DESC, total_playtime DESC
|
||||
LIMIT :limit
|
||||
");
|
||||
$stmt->execute(['limit' => $limit]);
|
||||
$games = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Enhance each game with platform details
|
||||
foreach ($games as &$game) {
|
||||
$game['platforms'] = array_unique(explode(',', $game['platforms']));
|
||||
$game['source_ids'] = array_unique(explode(',', $game['source_ids']));
|
||||
$game['genres'] = array_unique(array_filter(explode(',', $game['genres'])));
|
||||
}
|
||||
|
||||
return $games;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update platform-specific achievements
|
||||
*/
|
||||
public function updatePlatformAchievements(array $achievements): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'platform_achievements' => json_encode($achievements)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update platform-specific statistics
|
||||
*/
|
||||
public function updatePlatformStats(array $stats): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'platform_stats' => json_encode($stats)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get platform-specific achievements
|
||||
*/
|
||||
public function getPlatformAchievements(): array
|
||||
{
|
||||
return $this->platform_achievements ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get platform-specific statistics
|
||||
*/
|
||||
public function getPlatformStats(): array
|
||||
{
|
||||
return $this->platform_stats ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total count of games for pagination
|
||||
*/
|
||||
public static function getTotalCount(\PDO $pdo, string $search = ''): int
|
||||
{
|
||||
$sql = "SELECT COUNT(*) as count FROM games";
|
||||
$params = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$sql .= " WHERE title LIKE :search";
|
||||
$params['search'] = "%{$search}%";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return (int) $stmt->fetch()['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get grouped games with pagination and search support
|
||||
*/
|
||||
public static function getGroupedGamesWithPagination(\PDO $pdo, int $page, int $perPage, string $search = ''): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
game_key,
|
||||
title,
|
||||
COUNT(*) as platform_count,
|
||||
GROUP_CONCAT(DISTINCT platform ORDER BY platform) as platforms,
|
||||
GROUP_CONCAT(DISTINCT source_id ORDER BY source_id) as source_ids,
|
||||
MAX(image_url) as image_url,
|
||||
MAX(last_played_at) as last_played_at,
|
||||
SUM(playtime_minutes) as total_playtime,
|
||||
MAX(completion_percentage) as max_completion,
|
||||
GROUP_CONCAT(DISTINCT genre ORDER BY genre) as genres
|
||||
FROM games
|
||||
WHERE game_key IS NOT NULL
|
||||
";
|
||||
|
||||
$params = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$sql .= " AND title LIKE :search";
|
||||
$params['search'] = "%{$search}%";
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY game_key, title ORDER BY last_played_at DESC, total_playtime DESC LIMIT :limit OFFSET :offset";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$stmt->bindValue(":{$key}", $value);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
$games = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Enhance each game with platform details
|
||||
foreach ($games as &$game) {
|
||||
$game['platforms'] = array_unique(explode(',', $game['platforms']));
|
||||
$game['source_ids'] = array_unique(explode(',', $game['source_ids']));
|
||||
$game['genres'] = array_unique(array_filter(explode(',', $game['genres'])));
|
||||
}
|
||||
|
||||
return $games;
|
||||
}
|
||||
}
|
||||
108
app/Models/Model.php
Normal file
108
app/Models/Model.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use PDOException;
|
||||
|
||||
abstract class Model
|
||||
{
|
||||
protected \PDO $pdo;
|
||||
protected string $table;
|
||||
protected array $fillable = [];
|
||||
protected array $hidden = [];
|
||||
protected array $casts = [];
|
||||
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function find(int $id): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM {$this->table} WHERE id = :id");
|
||||
$stmt->execute(['id' => $id]);
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
public function findAll(array $conditions = []): array
|
||||
{
|
||||
$whereClause = $this->buildWhereClause($conditions);
|
||||
$sql = "SELECT * FROM {$this->table} {$whereClause['sql']}";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($whereClause['params']);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function create(array $data): ?int
|
||||
{
|
||||
$filteredData = array_intersect_key($data, array_flip($this->fillable));
|
||||
$columns = array_keys($filteredData);
|
||||
$placeholders = array_map(fn($col) => ":$col", $columns);
|
||||
$sql = "INSERT INTO {$this->table} (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($filteredData);
|
||||
return (int) $this->pdo->lastInsertId();
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): bool
|
||||
{
|
||||
$filteredData = array_intersect_key($data, array_flip($this->fillable));
|
||||
if (empty($filteredData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$setClause = array_map(fn($col) => "$col = :$col", array_keys($filteredData));
|
||||
$sql = "UPDATE {$this->table} SET " . implode(', ', $setClause) . " WHERE id = :id";
|
||||
$filteredData['id'] = $id;
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
return $stmt->execute($filteredData);
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE id = :id");
|
||||
return $stmt->execute(['id' => $id]);
|
||||
}
|
||||
|
||||
public function count(array $conditions = []): int
|
||||
{
|
||||
$whereClause = $this->buildWhereClause($conditions);
|
||||
$sql = "SELECT COUNT(*) FROM {$this->table} {$whereClause['sql']}";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($whereClause['params']);
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
protected function buildWhereClause(array $conditions): array
|
||||
{
|
||||
if (empty($conditions)) {
|
||||
return ['sql' => '', 'params' => []];
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
$params = [];
|
||||
|
||||
foreach ($conditions as $column => $value) {
|
||||
if (is_array($value)) {
|
||||
$parts[] = "$column IN (" . implode(', ', array_map(fn($k) => ":$column$k", array_keys($value))) . ")";
|
||||
foreach ($value as $k => $v) {
|
||||
$params["$column$k"] = $v;
|
||||
}
|
||||
} else {
|
||||
$parts[] = "$column = :$column";
|
||||
$params[$column] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'sql' => 'WHERE ' . implode(' AND ', $parts),
|
||||
'params' => $params
|
||||
];
|
||||
}
|
||||
|
||||
public function getTable(): string
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
}
|
||||
165
app/Models/Movie.php
Normal file
165
app/Models/Movie.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Movie extends Model
|
||||
{
|
||||
protected string $table = 'movies';
|
||||
protected array $fillable = [
|
||||
'title',
|
||||
'overview',
|
||||
'director',
|
||||
'writer',
|
||||
'genre',
|
||||
'cast',
|
||||
'release_date',
|
||||
'runtime_minutes',
|
||||
'rating',
|
||||
'imdb_id',
|
||||
'tmdb_id',
|
||||
'poster_url',
|
||||
'backdrop_url',
|
||||
'watched',
|
||||
'watch_count',
|
||||
'is_favorite',
|
||||
'metadata',
|
||||
'source_id',
|
||||
'last_watched_at'
|
||||
];
|
||||
|
||||
protected array $casts = [
|
||||
'runtime_minutes' => 'int',
|
||||
'rating' => 'float',
|
||||
'watched' => 'bool',
|
||||
'watch_count' => 'int',
|
||||
'is_favorite' => 'bool',
|
||||
'release_date' => 'date',
|
||||
'last_watched_at' => 'datetime'
|
||||
];
|
||||
|
||||
public function source()
|
||||
{
|
||||
return new Source($this->pdo);
|
||||
}
|
||||
|
||||
public function markAsWatched(): bool
|
||||
{
|
||||
$this->watched = true;
|
||||
$this->watch_count += 1;
|
||||
$this->last_watched_at = date('Y-m-d H:i:s');
|
||||
return $this->update($this->id, [
|
||||
'watched' => $this->watched,
|
||||
'watch_count' => $this->watch_count,
|
||||
'last_watched_at' => $this->last_watched_at
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAsUnwatched(): bool
|
||||
{
|
||||
$this->watched = false;
|
||||
return $this->update($this->id, [
|
||||
'watched' => $this->watched
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleFavorite(): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'is_favorite' => !$this->is_favorite
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateRating(float $rating): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'rating' => min(10.0, max(0.0, $rating))
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getStats(\PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
COUNT(*) as total_movies,
|
||||
COUNT(CASE WHEN watched = 1 THEN 1 END) as watched_movies,
|
||||
SUM(watch_count) as total_watches,
|
||||
AVG(rating) as avg_rating,
|
||||
COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_movies,
|
||||
SUM(runtime_minutes) as total_runtime
|
||||
FROM movies
|
||||
");
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public static function getRecent(\PDO $pdo, int $limit = 10): array
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT m.*, s.display_name as source_name
|
||||
FROM movies m
|
||||
JOIN sources s ON m.source_id = s.id
|
||||
WHERE m.last_watched_at IS NOT NULL
|
||||
ORDER BY m.last_watched_at DESC
|
||||
LIMIT :limit
|
||||
");
|
||||
$stmt->execute(['limit' => $limit]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public static function getTotalCount(\PDO $pdo, string $search = ''): int
|
||||
{
|
||||
$sql = "SELECT COUNT(*) as count FROM movies m JOIN sources s ON m.source_id = s.id";
|
||||
$params = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$sql .= " WHERE m.title LIKE :search";
|
||||
$params['search'] = "%{$search}%";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return (int) $stmt->fetch()['count'];
|
||||
}
|
||||
|
||||
public static function getAllWithPagination(\PDO $pdo, int $page, int $perPage, string $search = ''): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$sql = "
|
||||
SELECT m.*, s.display_name as source_name
|
||||
FROM movies m
|
||||
JOIN sources s ON m.source_id = s.id
|
||||
";
|
||||
$params = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$sql .= " WHERE m.title LIKE :search";
|
||||
$params['search'] = "%{$search}%";
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY m.title ASC LIMIT :limit OFFSET :offset";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$stmt->bindValue(":{$key}", $value);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public static function getAll(\PDO $pdo, int $limit = 100): array
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT m.*, s.display_name as source_name
|
||||
FROM movies m
|
||||
JOIN sources s ON m.source_id = s.id
|
||||
ORDER BY m.title ASC
|
||||
LIMIT :limit
|
||||
");
|
||||
$stmt->execute(['limit' => $limit]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
103
app/Models/Source.php
Normal file
103
app/Models/Source.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Source extends Model
|
||||
{
|
||||
protected string $table = 'sources';
|
||||
protected array $fillable = [
|
||||
'name',
|
||||
'display_name',
|
||||
'api_url',
|
||||
'api_key',
|
||||
'config',
|
||||
'is_active',
|
||||
'last_sync_at'
|
||||
];
|
||||
|
||||
public function games(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM games WHERE source_id = :source_id");
|
||||
$stmt->execute(['source_id' => $this->id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function movies(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM movies WHERE source_id = :source_id");
|
||||
$stmt->execute(['source_id' => $this->id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function tvShows(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM tv_shows WHERE source_id = :source_id");
|
||||
$stmt->execute(['source_id' => $this->id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function musicArtists(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM music_artists WHERE source_id = :source_id");
|
||||
$stmt->execute(['source_id' => $this->id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function getSyncLogs(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM sync_logs WHERE source_id = :source_id ORDER BY created_at DESC");
|
||||
$stmt->execute(['source_id' => $this->id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function createSyncLog(string $syncType, string $status): int
|
||||
{
|
||||
$data = [
|
||||
'source_id' => $this->id,
|
||||
'sync_type' => $syncType,
|
||||
'status' => $status,
|
||||
'total_items' => 0,
|
||||
'processed_items' => 0,
|
||||
'new_items' => 0,
|
||||
'updated_items' => 0,
|
||||
'deleted_items' => 0,
|
||||
'started_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$columns = array_keys($data);
|
||||
$placeholders = array_map(fn($col) => ":$col", $columns);
|
||||
$sql = "INSERT INTO sync_logs (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($data);
|
||||
|
||||
return (int) $this->pdo->lastInsertId();
|
||||
}
|
||||
|
||||
public function updateSyncLog(int $syncLogId, string $status, array $stats = []): bool
|
||||
{
|
||||
$data = [
|
||||
'status' => $status,
|
||||
'processed_items' => $stats['processed_items'] ?? 0,
|
||||
'new_items' => $stats['new_items'] ?? 0,
|
||||
'updated_items' => $stats['updated_items'] ?? 0,
|
||||
'deleted_items' => $stats['deleted_items'] ?? 0,
|
||||
'completed_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
if (!empty($stats['errors'])) {
|
||||
$data['errors'] = json_encode($stats['errors']);
|
||||
}
|
||||
|
||||
if (!empty($stats['message'])) {
|
||||
$data['message'] = $stats['message'];
|
||||
}
|
||||
|
||||
$setClause = array_map(fn($col) => "$col = :$col", array_keys($data));
|
||||
$sql = "UPDATE sync_logs SET " . implode(', ', $setClause) . " WHERE id = :id";
|
||||
$data['id'] = $syncLogId;
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
return $stmt->execute($data);
|
||||
}
|
||||
}
|
||||
90
app/Models/SyncLog.php
Normal file
90
app/Models/SyncLog.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class SyncLog extends Model
|
||||
{
|
||||
protected string $table = 'sync_logs';
|
||||
protected array $fillable = [
|
||||
'source_id',
|
||||
'sync_type',
|
||||
'status',
|
||||
'total_items',
|
||||
'processed_items',
|
||||
'new_items',
|
||||
'updated_items',
|
||||
'deleted_items',
|
||||
'errors',
|
||||
'message',
|
||||
'started_at',
|
||||
'completed_at'
|
||||
];
|
||||
|
||||
protected array $casts = [
|
||||
'errors' => 'array',
|
||||
'started_at' => 'datetime',
|
||||
'completed_at' => 'datetime'
|
||||
];
|
||||
|
||||
public static function getRecent(\PDO $pdo, int $limit = 10): array
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT sl.*, s.display_name as source_name
|
||||
FROM sync_logs sl
|
||||
JOIN sources s ON sl.source_id = s.id
|
||||
ORDER BY sl.created_at DESC
|
||||
LIMIT :limit
|
||||
");
|
||||
$stmt->execute(['limit' => $limit]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function source()
|
||||
{
|
||||
return new Source($this->pdo);
|
||||
}
|
||||
|
||||
public function markAsStarted(): void
|
||||
{
|
||||
$this->update($this->id, [
|
||||
'status' => 'started',
|
||||
'started_at' => date('Y-m-d H:i:s'),
|
||||
'message' => null,
|
||||
'errors' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAsCompleted(array $stats = []): void
|
||||
{
|
||||
$data = [
|
||||
'status' => 'completed',
|
||||
'completed_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
if (!empty($stats)) {
|
||||
$data = array_merge($data, $stats);
|
||||
}
|
||||
|
||||
$this->update($this->id, $data);
|
||||
}
|
||||
|
||||
public function markAsFailed(string $errorMessage, array $errors = []): void
|
||||
{
|
||||
$this->update($this->id, [
|
||||
'status' => 'failed',
|
||||
'completed_at' => date('Y-m-d H:i:s'),
|
||||
'message' => $errorMessage,
|
||||
'errors' => !empty($errors) ? json_encode($errors) : null
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateProgress(int $processed, int $new = 0, int $updated = 0, int $deleted = 0): void
|
||||
{
|
||||
$this->update($this->id, [
|
||||
'processed_items' => $processed,
|
||||
'new_items' => $new,
|
||||
'updated_items' => $updated,
|
||||
'deleted_items' => $deleted
|
||||
]);
|
||||
}
|
||||
}
|
||||
92
app/Models/User.php
Normal file
92
app/Models/User.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
protected string $table = 'users';
|
||||
protected array $fillable = [
|
||||
'username',
|
||||
'email',
|
||||
'password',
|
||||
'role',
|
||||
'is_active',
|
||||
'last_login_at',
|
||||
'login_ip'
|
||||
];
|
||||
|
||||
protected array $hidden = [
|
||||
'password',
|
||||
'remember_token'
|
||||
];
|
||||
|
||||
protected array $casts = [
|
||||
'is_active' => 'bool',
|
||||
'last_login_at' => 'datetime'
|
||||
];
|
||||
|
||||
public function setPassword(string $password): void
|
||||
{
|
||||
$this->password = password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
public function verifyPassword(string $password): bool
|
||||
{
|
||||
return password_verify($password, $this->password);
|
||||
}
|
||||
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->role === 'admin';
|
||||
}
|
||||
|
||||
public function updateLastLogin(string $ip = null): bool
|
||||
{
|
||||
return $this->update($this->id, [
|
||||
'last_login_at' => date('Y-m-d H:i:s'),
|
||||
'login_ip' => $ip
|
||||
]);
|
||||
}
|
||||
|
||||
public static function findByUsername(\PDO $pdo, string $username): ?array
|
||||
{
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND is_active = 1");
|
||||
$stmt->execute(['username' => $username]);
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
public static function findByEmail(\PDO $pdo, string $email): ?array
|
||||
{
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND is_active = 1");
|
||||
$stmt->execute(['email' => $email]);
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
public static function createAdmin(\PDO $pdo, string $username, string $email, string $password): int
|
||||
{
|
||||
$data = [
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
'role' => 'admin',
|
||||
'is_active' => true
|
||||
];
|
||||
|
||||
$userModel = new self($pdo);
|
||||
$userModel->setPassword($password);
|
||||
$data['password'] = $userModel->password;
|
||||
|
||||
return $userModel->create($data);
|
||||
}
|
||||
|
||||
public static function getStats(\PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
COUNT(*) as total_users,
|
||||
COUNT(CASE WHEN role = 'admin' THEN 1 END) as admin_users,
|
||||
COUNT(CASE WHEN last_login_at IS NOT NULL THEN 1 END) as active_users
|
||||
FROM users
|
||||
");
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user