Add PHP Media API scaffold and Docker configs
Initial project scaffold for a PHP Media API including routing, controllers, models and services under api/ (Router, Media/Cast/Image/Settings controllers, models, database/bootstrap files and automatic docs service). Adds Docker support (Dockerfile, docker-compose.yml, DOCKER_README.md, php-custom.ini), .htaccess for pretty URLs, API documentation and example payloads (API_EXAMPLES.md, api/README.md, api_examples/*.json), image handling service and logging, plus a comprehensive .gitignore. This commit provides a runnable development environment and example requests to get the API up and tested quickly.
This commit is contained in:
291
api/models/Media.php
Normal file
291
api/models/Media.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/BaseModel.php';
|
||||
|
||||
class Media extends BaseModel {
|
||||
protected $table = 'media';
|
||||
|
||||
public function getBase($id) {
|
||||
return $this->findById($id);
|
||||
}
|
||||
|
||||
public function getWithRelations($id) {
|
||||
$media = $this->findById($id);
|
||||
if (!$media) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$media['genres'] = $this->getRelatedItems('genres', $id);
|
||||
$media['tags'] = $this->getRelatedItems('tags', $id);
|
||||
$media['studios'] = $this->getRelatedItems('studios', $id);
|
||||
$media['staff'] = $this->getCastForMedia($id);
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
public function search($filters = [], $page = 1, $limit = 20) {
|
||||
$conditions = [];
|
||||
|
||||
if (isset($filters['category'])) {
|
||||
$conditions['category'] = $filters['category'];
|
||||
}
|
||||
if (isset($filters['type'])) {
|
||||
$conditions['type'] = $filters['type'];
|
||||
}
|
||||
if (isset($filters['search'])) {
|
||||
$searchTerm = "%" . $filters['search'] . "%";
|
||||
$conditions['title'] = [$searchTerm];
|
||||
// OR Bedingung für description wird separat behandelt
|
||||
}
|
||||
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
if (isset($filters['search'])) {
|
||||
// Komplexe Suche mit OR
|
||||
$query = "SELECT * FROM {$this->table} WHERE 1=1";
|
||||
$params = [];
|
||||
|
||||
if (isset($filters['category'])) {
|
||||
$query .= " AND category = ?";
|
||||
$params[] = $filters['category'];
|
||||
}
|
||||
if (isset($filters['type'])) {
|
||||
$query .= " AND type = ?";
|
||||
$params[] = $filters['type'];
|
||||
}
|
||||
|
||||
$query .= " AND (title LIKE ? OR description LIKE ?)";
|
||||
$searchTerm = "%" . $filters['search'] . "%";
|
||||
$params[] = $searchTerm;
|
||||
$params[] = $searchTerm;
|
||||
|
||||
$query .= " ORDER BY createdAt DESC LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
||||
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$stmt->execute($params);
|
||||
$items = $stmt->fetchAll();
|
||||
|
||||
// Cast-Mitglieder für jedes Medium laden
|
||||
foreach ($items as &$item) {
|
||||
$item['staff'] = $this->getCastForMedia($item['id']);
|
||||
}
|
||||
|
||||
// Count Query
|
||||
$countQuery = "SELECT COUNT(*) FROM {$this->table} WHERE 1=1";
|
||||
$countParams = [];
|
||||
|
||||
if (isset($filters['category'])) {
|
||||
$countQuery .= " AND category = ?";
|
||||
$countParams[] = $filters['category'];
|
||||
}
|
||||
if (isset($filters['type'])) {
|
||||
$countQuery .= " AND type = ?";
|
||||
$countParams[] = $filters['type'];
|
||||
}
|
||||
|
||||
$countQuery .= " AND (title LIKE ? OR description LIKE ?)";
|
||||
$countParams[] = $searchTerm;
|
||||
$countParams[] = $searchTerm;
|
||||
|
||||
$countStmt = $this->pdo->prepare($countQuery);
|
||||
$countStmt->execute($countParams);
|
||||
$total = $countStmt->fetchColumn();
|
||||
} else {
|
||||
$items = $this->findAll($conditions, 'createdAt DESC', $limit, $offset);
|
||||
$total = $this->count($conditions);
|
||||
|
||||
// Cast-Mitglieder für jedes Medium laden
|
||||
foreach ($items as &$item) {
|
||||
$item['staff'] = $this->getCastForMedia($item['id']);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'totalPages' => ceil($total / $limit)
|
||||
];
|
||||
}
|
||||
|
||||
public function createWithRelations($data) {
|
||||
$title = $data['title'] ?? null;
|
||||
if (!$title) {
|
||||
throw new Exception('Title is required');
|
||||
}
|
||||
|
||||
// cleanname generieren
|
||||
$cleanname = generateCleanName($title);
|
||||
|
||||
// Basis-Media-Daten extrahieren
|
||||
$mediaData = [
|
||||
'title' => $title,
|
||||
'cleanname' => $cleanname,
|
||||
'year' => $data['year'] ?? null,
|
||||
'poster' => $data['poster'] ?? null,
|
||||
'banner' => $data['banner'] ?? null,
|
||||
'description' => $data['description'] ?? null,
|
||||
'rating' => $data['rating'] ?? null,
|
||||
'category' => $data['category'] ?? null,
|
||||
'type' => $data['type'] ?? null,
|
||||
'status' => $data['status'] ?? null,
|
||||
'aspectRatio' => $data['aspectRatio'] ?? null,
|
||||
'runtime' => $data['runtime'] ?? null,
|
||||
'director' => $data['director'] ?? null,
|
||||
'writer' => $data['writer'] ?? null,
|
||||
'source' => $data['source'] ?? null,
|
||||
'releaseDate' => $data['releaseDate'] ?? null
|
||||
];
|
||||
|
||||
$mediaId = $this->create($mediaData);
|
||||
|
||||
// Relationen speichern
|
||||
if (isset($data['genres']) && is_array($data['genres'])) {
|
||||
$this->saveRelatedItems('genres', $mediaId, $data['genres']);
|
||||
}
|
||||
if (isset($data['tags']) && is_array($data['tags'])) {
|
||||
$this->saveRelatedItems('tags', $mediaId, $data['tags']);
|
||||
}
|
||||
if (isset($data['studios']) && is_array($data['studios'])) {
|
||||
$this->saveRelatedItems('studios', $mediaId, $data['studios']);
|
||||
}
|
||||
if (isset($data['staff']) && is_array($data['staff'])) {
|
||||
$this->saveCastAssignments($mediaId, $data['staff']);
|
||||
}
|
||||
|
||||
return $mediaId;
|
||||
}
|
||||
|
||||
public function updateWithRelations($id, $data) {
|
||||
$mediaData = [];
|
||||
|
||||
foreach (['title', 'year', 'poster', 'banner', 'description', 'rating', 'category', 'type', 'status', 'aspectRatio', 'runtime', 'director', 'writer', 'releaseDate', 'source'] as $field) {
|
||||
if (array_key_exists($field, $data)) {
|
||||
$mediaData[$field] = $data[$field];
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn title geändert wurde, cleanname aktualisieren
|
||||
if (isset($data['title'])) {
|
||||
$mediaData['cleanname'] = generateCleanName($data['title']);
|
||||
}
|
||||
|
||||
if (!empty($mediaData)) {
|
||||
$this->update($id, $mediaData);
|
||||
}
|
||||
|
||||
// Relationen aktualisieren
|
||||
if (isset($data['genres']) && is_array($data['genres'])) {
|
||||
$this->pdo->prepare("DELETE FROM genres WHERE media_id = ?")->execute([$id]);
|
||||
$this->saveRelatedItems('genres', $id, $data['genres']);
|
||||
}
|
||||
if (isset($data['tags']) && is_array($data['tags'])) {
|
||||
$this->pdo->prepare("DELETE FROM tags WHERE media_id = ?")->execute([$id]);
|
||||
$this->saveRelatedItems('tags', $id, $data['tags']);
|
||||
}
|
||||
if (isset($data['studios']) && is_array($data['studios'])) {
|
||||
$this->pdo->prepare("DELETE FROM studios WHERE media_id = ?")->execute([$id]);
|
||||
$this->saveRelatedItems('studios', $id, $data['studios']);
|
||||
}
|
||||
if (isset($data['staff']) && is_array($data['staff'])) {
|
||||
$this->pdo->prepare("DELETE FROM media_cast WHERE media_id = ?")->execute([$id]);
|
||||
$this->saveCastAssignments($id, $data['staff']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function findByCleanName($cleanname) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM {$this->table} WHERE cleanname = ?");
|
||||
$stmt->execute([$cleanname]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
protected function saveRelatedItems($table, $id, $items, $fkColumn = 'media_id') {
|
||||
$valueColumn = $table === 'genres' ? 'genre' : ($table === 'tags' ? 'tag' : ($table === 'occupations' ? 'occupation' : 'studio'));
|
||||
$stmt = $this->pdo->prepare("INSERT INTO $table ($fkColumn, $valueColumn) VALUES (?, ?)");
|
||||
foreach ($items as $item) {
|
||||
$stmt->execute([$id, $item]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCastForMedia($mediaId) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT cs.*, mc.role, mc.characterName, mc.characterImage
|
||||
FROM cast_staff cs
|
||||
INNER JOIN media_cast mc ON cs.id = mc.cast_id
|
||||
WHERE mc.media_id = ?
|
||||
");
|
||||
$stmt->execute([$mediaId]);
|
||||
$cast = $stmt->fetchAll();
|
||||
foreach ($cast as &$member) {
|
||||
$member['occupations'] = $this->getRelatedItems('occupations', $member['id'], 'cast_id');
|
||||
}
|
||||
return $cast;
|
||||
}
|
||||
|
||||
protected function getRelatedItems($table, $id, $fkColumn = 'media_id') {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM $table WHERE $fkColumn = ?");
|
||||
$stmt->execute([$id]);
|
||||
$items = $stmt->fetchAll();
|
||||
$result = [];
|
||||
foreach ($items as $item) {
|
||||
if ($fkColumn === 'cast_id') {
|
||||
$result[] = $item['occupation'];
|
||||
} else {
|
||||
$result[] = $table === 'genres' ? $item['genre'] : ($table === 'tags' ? $item['tag'] : $item['studio']);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function saveCastAssignments($mediaId, $castData) {
|
||||
foreach ($castData as $member) {
|
||||
$castId = null;
|
||||
if (isset($member['id']) && $member['id']) {
|
||||
$castId = $member['id'];
|
||||
} elseif (isset($member['name'])) {
|
||||
$stmt = $this->pdo->prepare("SELECT id FROM cast_staff WHERE name = ? LIMIT 1");
|
||||
$stmt->execute([$member['name']]);
|
||||
$existing = $stmt->fetch();
|
||||
if ($existing) {
|
||||
$castId = $existing['id'];
|
||||
} else {
|
||||
$cleanname = generateCleanName($member['name']);
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO cast_staff (name, cleanname, photo, bio, birthDate, birthPlace)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([
|
||||
$member['name'] ?? null,
|
||||
$cleanname,
|
||||
$member['photo'] ?? null,
|
||||
$member['bio'] ?? null,
|
||||
$member['birthDate'] ?? null,
|
||||
$member['birthPlace'] ?? null
|
||||
]);
|
||||
$castId = $this->pdo->lastInsertId();
|
||||
|
||||
if (isset($member['occupations']) && is_array($member['occupations'])) {
|
||||
$this->saveRelatedItems('occupations', $castId, $member['occupations'], 'cast_id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($castId) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO media_cast (media_id, cast_id, role, characterName, characterImage)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([
|
||||
$mediaId,
|
||||
$castId,
|
||||
$member['role'] ?? null,
|
||||
$member['characterName'] ?? null,
|
||||
$member['characterImage'] ?? null
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user