From 728ca893b11dbce36179c40523f56abb9b1d23cb Mon Sep 17 00:00:00 2001 From: Lars Behrends Date: Sun, 12 Apr 2026 03:00:17 +0200 Subject: [PATCH] Add API documentation and project README Add comprehensive API_DOCS.md describing the Kyoo backend API (endpoints, request/response examples, image handling, authentication, error handling, CORS, logging) and a README.md with project overview, tech stack, quick start, API endpoints, data models, configuration and Docker instructions. Provides developers with usage examples, directory layout and troubleshooting steps for local deployment. --- API_DOCS.md | 1112 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 331 +++++++++++++++ 2 files changed, 1443 insertions(+) create mode 100644 API_DOCS.md create mode 100644 README.md diff --git a/API_DOCS.md b/API_DOCS.md new file mode 100644 index 0000000..1fc4500 --- /dev/null +++ b/API_DOCS.md @@ -0,0 +1,1112 @@ +# Kyoo Backend API Documentation + +**Base URL**: `http://localhost:6400/api` + +**Content-Type**: `application/json` + +--- + +## Table of Contents + +- [Response Format](#response-format) +- [Authentication](#authentication) +- [Endpoints](#endpoints) + - [Root](#root) + - [Documentation](#documentation) + - [Media](#media) + - [Series Episodes](#series-episodes) + - [Music Tracks](#music-tracks) + - [Cast](#cast) + - [Adult Cast](#adult-cast) + - [Images](#images) + - [Settings](#settings) + +--- + +## Response Format + +### Success Response +```json +{ + "success": true, + "data": { ... } +} +``` + +### Error Response +```json +{ + "success": false, + "error": "Error message" +} +``` + +### Paginated Response +```json +{ + "success": true, + "data": { + "items": [ ... ], + "total": 100, + "page": 1, + "limit": 20, + "totalPages": 5 + } +} +``` + +### HTTP Status Codes +- `200` - Success +- `201` - Created +- `400` - Bad Request +- `404` - Not Found +- `405` - Method Not Allowed +- `500` - Internal Server Error + +--- + +## Authentication + +Currently, the API does not require authentication. All endpoints are publicly accessible. + +--- + +## Endpoints + +### Root + +#### Get API Information + +**Endpoint**: `GET /api` + +**Description**: Returns API version and available endpoints. + +**Response**: +```json +{ + "success": true, + "message": "Media API v1.0", + "endpoints": { + "GET /api/docs": "Automatische API-Dokumentation", + "GET /api/images/*": "Bilder abrufen", + "GET /api/media": "Alle Medien abrufen", + "GET /api/media/:id": "Ein Medium abrufen", + "POST /api/media": "Neues Medium erstellen", + "PUT /api/media/:id": "Medium aktualisieren", + "DELETE /api/media/:id": "Medium löschen", + "GET /api/cast": "Alle Cast-Mitglieder abrufen", + "GET /api/cast/:id": "Cast-Mitglied abrufen", + "GET /api/cast/:id/media": "Alle Medien eines Cast-Mitglieds abrufen", + "POST /api/cast": "Neues Cast-Mitglied erstellen", + "PUT /api/cast/:id": "Cast-Mitglied aktualisieren", + "DELETE /api/cast/:id": "Cast-Mitglied löschen", + "GET /api/settings": "Einstellungen abrufen", + "PUT /api/settings": "Einstellungen aktualisieren" + } +} +``` + +--- + +### Documentation + +#### Get Auto-Generated Documentation + +**Endpoint**: `GET /api/docs` + +**Description**: Returns automatically generated API documentation. + +**Response**: +```json +{ + "success": true, + "data": { ... } +} +``` + +--- + +### Media + +#### Get All Media + +**Endpoint**: `GET /api/media` + +**Query Parameters**: +- `page` (integer, optional) - Page number (default: 1) +- `limit` (integer, optional) - Items per page (default: 20) +- `category` (string, optional) - Filter by category (e.g., "movie", "tv", "music", "game") +- `type` (string, optional) - Filter by type (e.g., "Movie", "TV", "Album", "Game") +- `search` (string, optional) - Search in title and description + +**Example Request**: +```bash +GET /api/media?page=1&limit=20&category=movie&search=action +``` + +**Response**: +```json +{ + "success": true, + "data": { + "items": [ + { + "id": 1, + "title": "Movie Title", + "cleanname": "movie-title", + "year": 2024, + "poster": "/images/movies/poster_xxx.webp", + "banner": "/images/movies/banner_xxx.webp", + "description": "Description text", + "rating": 8.5, + "category": "movie", + "type": "Movie", + "status": "released", + "aspectRatio": "16:9", + "runtime": 120, + "director": "Director Name", + "writer": "Writer Name", + "source": "netflix", + "releaseDate": "2024-01-01", + "createdAt": "2024-01-01 00:00:00", + "updatedAt": "2024-01-01 00:00:00" + } + ], + "total": 100, + "page": 1, + "limit": 20, + "totalPages": 5 + } +} +``` + +#### Get Single Media + +**Endpoint**: `GET /api/media/:id` + +**Path Parameters**: +- `id` (integer, required) - Media ID + +**Example Request**: +```bash +GET /api/media/1 +``` + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1, + "title": "Movie Title", + "cleanname": "movie-title", + "year": 2024, + "poster": "/images/movies/poster_xxx.webp", + "banner": "/images/movies/banner_xxx.webp", + "description": "Description text", + "rating": 8.5, + "category": "movie", + "type": "Movie", + "status": "released", + "aspectRatio": "16:9", + "runtime": 120, + "director": "Director Name", + "writer": "Writer Name", + "source": "netflix", + "releaseDate": "2024-01-01", + "genres": ["Action", "Drama"], + "tags": ["tag1", "tag2"], + "studios": ["Studio1"], + "staff": [ + { + "id": 1, + "name": "Actor Name", + "photo": "/images/cast/photo_xxx.webp", + "role": "Actor", + "characterName": "Character", + "characterImage": "/images/characters/char_xxx.webp" + } + ], + "createdAt": "2024-01-01 00:00:00", + "updatedAt": "2024-01-01 00:00:00" + } +} +``` + +#### Create Media + +**Endpoint**: `POST /api/media` + +**Request Body**: +```json +{ + "title": "Movie Title", + "year": 2024, + "poster": "base64_encoded_image_or_url", + "banner": "base64_encoded_image_or_url", + "description": "Description text", + "rating": 8.5, + "category": "movie", + "type": "Movie", + "status": "released", + "aspectRatio": "16:9", + "runtime": 120, + "director": "Director Name", + "writer": "Writer Name", + "source": "netflix", + "releaseDate": "2024-01-01", + "genres": ["Action", "Drama"], + "tags": ["tag1", "tag2"], + "studios": ["Studio1"], + "staff": [ + { + "id": 1, + "role": "Actor", + "characterName": "Character", + "characterImage": "base64_encoded_image_or_url" + } + ] +} +``` + +**Note**: If `staff` includes a cast member by `name` instead of `id`, the cast member will be automatically created if it doesn't exist. + +**Response** (201 Created): +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +**Response** (200 OK - if media already exists): +```json +{ + "success": true, + "data": { + "id": 1, + "message": "Media already exists" + } +} +``` + +#### Update Media + +**Endpoint**: `PUT /api/media/:id` + +**Path Parameters**: +- `id` (integer, required) - Media ID + +**Request Body**: Same as create, but all fields are optional. + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +#### Delete Media + +**Endpoint**: `DELETE /api/media/:id` + +**Path Parameters**: +- `id` (integer, required) - Media ID + +**Response**: +```json +{ + "success": true, + "message": "Media deleted successfully" +} +``` + +--- + +### Series Episodes + +#### Get All Episodes + +**Endpoint**: `GET /api/media/:id/episodes` + +**Path Parameters**: +- `id` (integer, required) - Series media ID + +**Query Parameters**: +- `season` (integer, optional) - Filter by season number + +**Example Request**: +```bash +GET /api/media/1/episodes?season=1 +``` + +**Response**: +```json +{ + "success": true, + "data": { + "items": [ + { + "id": 1, + "media_id": 1, + "season": 1, + "episode": 1, + "title": "Episode Title", + "overview": "Episode description", + "airDate": "2024-01-01", + "runtime": 45, + "poster": "/images/episodes/poster_xxx.webp" + } + ] + } +} +``` + +#### Get Single Episode + +**Endpoint**: `GET /api/media/:id/episodes/:episodeId` + +**Path Parameters**: +- `id` (integer, required) - Series media ID +- `episodeId` (integer, required) - Episode ID + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1, + "media_id": 1, + "season": 1, + "episode": 1, + "title": "Episode Title", + "overview": "Episode description", + "airDate": "2024-01-01", + "runtime": 45, + "poster": "/images/episodes/poster_xxx.webp" + } +} +``` + +#### Add Episode + +**Endpoint**: `POST /api/media/:id/episodes` + +**Path Parameters**: +- `id` (integer, required) - Series media ID + +**Request Body**: +```json +{ + "season": 1, + "episode": 1, + "title": "Episode Title", + "overview": "Episode description", + "airDate": "2024-01-01", + "runtime": 45, + "poster": "base64_encoded_image_or_url" +} +``` + +**Response** (201 Created): +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +#### Update Episode + +**Endpoint**: `PUT /api/media/:id/episodes/:episodeId` + +**Path Parameters**: +- `id` (integer, required) - Series media ID +- `episodeId` (integer, required) - Episode ID + +**Request Body**: Same as add episode, but all fields are optional. + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +#### Delete Episode + +**Endpoint**: `DELETE /api/media/:id/episodes/:episodeId` + +**Path Parameters**: +- `id` (integer, required) - Series media ID +- `episodeId` (integer, required) - Episode ID + +**Response**: +```json +{ + "success": true, + "message": "Episode deleted successfully" +} +``` + +--- + +### Music Tracks + +#### Get All Tracks + +**Endpoint**: `GET /api/media/:id/tracks` + +**Path Parameters**: +- `id` (integer, required) - Album media ID + +**Response**: +```json +{ + "success": true, + "data": { + "items": [ + { + "id": 1, + "media_id": 1, + "track": 1, + "title": "Track Title", + "duration": 240, + "artists": ["Artist1", "Artist2"] + } + ] + } +} +``` + +#### Get Single Track + +**Endpoint**: `GET /api/media/:id/tracks/:trackId` + +**Path Parameters**: +- `id` (integer, required) - Album media ID +- `trackId` (integer, required) - Track ID + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1, + "media_id": 1, + "track": 1, + "title": "Track Title", + "duration": 240, + "artists": ["Artist1", "Artist2"] + } +} +``` + +#### Add Track + +**Endpoint**: `POST /api/media/:id/tracks` + +**Path Parameters**: +- `id` (integer, required) - Album media ID + +**Request Body**: +```json +{ + "track": 1, + "title": "Track Title", + "duration": 240, + "artists": ["Artist1", "Artist2"] +} +``` + +**Response** (201 Created): +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +#### Update Track + +**Endpoint**: `PUT /api/media/:id/tracks/:trackId` + +**Path Parameters**: +- `id` (integer, required) - Album media ID +- `trackId` (integer, required) - Track ID + +**Request Body**: Same as add track, but all fields are optional. + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +#### Delete Track + +**Endpoint**: `DELETE /api/media/:id/tracks/:trackId` + +**Path Parameters**: +- `id` (integer, required) - Album media ID +- `trackId` (integer, required) - Track ID + +**Response**: +```json +{ + "success": true, + "message": "Track deleted successfully" +} +``` + +--- + +### Cast + +#### Get All Cast + +**Endpoint**: `GET /api/cast` + +**Query Parameters**: +- `page` (integer, optional) - Page number (default: 1) +- `limit` (integer, optional) - Items per page (default: 20) +- `search` (string, optional) - Search by name + +**Example Request**: +```bash +GET /api/cast?page=1&limit=20&search=john +``` + +**Response**: +```json +{ + "success": true, + "data": { + "items": [ + { + "id": 1, + "name": "Actor Name", + "cleanname": "actor-name", + "photo": "/images/cast/photo_xxx.webp", + "bio": "Biography text", + "birthDate": "1990-01-01", + "birthPlace": "City, Country", + "occupations": ["Actor", "Director"], + "filmography": [ + { + "id": 1, + "title": "Movie Title", + "year": 2024, + "poster": "/images/movies/poster_xxx.webp", + "category": "movie", + "type": "Movie", + "role": "Actor", + "characterName": "Character" + } + ], + "media_types": ["movie", "tv"], + "createdAt": "2024-01-01 00:00:00", + "updatedAt": "2024-01-01 00:00:00" + } + ], + "total": 100, + "page": 1, + "limit": 20 + } +} +``` + +#### Get Single Cast + +**Endpoint**: `GET /api/cast/:id` + +**Path Parameters**: +- `id` (integer, required) - Cast ID + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1, + "name": "Actor Name", + "cleanname": "actor-name", + "photo": "/images/cast/photo_xxx.webp", + "bio": "Biography text", + "birthDate": "1990-01-01", + "birthPlace": "City, Country", + "occupations": ["Actor", "Director"], + "filmography": [ + { + "id": 1, + "title": "Movie Title", + "year": 2024, + "poster": "/images/movies/poster_xxx.webp", + "category": "movie", + "type": "Movie", + "role": "Actor", + "characterName": "Character" + } + ], + "adult_specifics": { + "id": 1, + "cast_id": 1, + "ethnicity": "Asian", + "hair_color": "Black", + "eye_color": "Brown", + "height": 170, + "weight": 60 + }, + "createdAt": "2024-01-01 00:00:00", + "updatedAt": "2024-01-01 00:00:00" + } +} +``` + +#### Get Cast Media + +**Endpoint**: `GET /api/cast/:id/media` + +**Path Parameters**: +- `id` (integer, required) - Cast ID + +**Response**: +```json +{ + "success": true, + "data": { + "items": [ + { + "id": 1, + "title": "Movie Title", + "year": 2024, + "poster": "/images/movies/poster_xxx.webp", + "category": "movie", + "type": "Movie", + "role": "Actor", + "characterName": "Character" + } + ] + } +} +``` + +#### Create Cast + +**Endpoint**: `POST /api/cast` + +**Request Body**: +```json +{ + "name": "Actor Name", + "photo": "base64_encoded_image_or_url", + "bio": "Biography text", + "birthDate": "1990-01-01", + "birthPlace": "City, Country", + "occupations": ["Actor", "Director"] +} +``` + +**Response** (201 Created): +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +**Response** (200 OK - if cast already exists): +```json +{ + "success": true, + "data": { + "id": 1, + "message": "Cast already exists" + } +} +``` + +#### Update Cast + +**Endpoint**: `PUT /api/cast/:id` + +**Path Parameters**: +- `id` (integer, required) - Cast ID + +**Request Body**: Same as create, but all fields are optional. + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +#### Delete Cast + +**Endpoint**: `DELETE /api/cast/:id` + +**Path Parameters**: +- `id` (integer, required) - Cast ID + +**Response**: +```json +{ + "success": true, + "message": "Cast member deleted successfully" +} +``` + +--- + +### Adult Cast + +#### Get All Adult Cast + +**Endpoint**: `GET /api/cast/adult` + +**Query Parameters**: +- `page` (integer, optional) - Page number (default: 1) +- `limit` (integer, optional) - Items per page (default: 20) +- `search` (string, optional) - Search by name +- `ethnicity` (string, optional) - Filter by ethnicity +- `hair_color` (string, optional) - Filter by hair color + +**Example Request**: +```bash +GET /api/cast/adult?page=1&limit=20ðnicity=Asian +``` + +**Response**: +```json +{ + "success": true, + "data": { + "items": [ + { + "id": 1, + "name": "Actor Name", + "cleanname": "actor-name", + "photo": "/images/cast/photo_xxx.webp", + "bio": "Biography text", + "birthDate": "1990-01-01", + "birthPlace": "City, Country", + "ethnicity": "Asian", + "hair_color": "Black", + "eye_color": "Brown", + "height": 170, + "weight": 60, + "createdAt": "2024-01-01 00:00:00", + "updatedAt": "2024-01-01 00:00:00" + } + ], + "total": 100, + "page": 1, + "limit": 20 + } +} +``` + +#### Get Single Adult Cast + +**Endpoint**: `GET /api/cast/adult/:id` + +**Path Parameters**: +- `id` (integer, required) - Cast ID + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1, + "name": "Actor Name", + "cleanname": "actor-name", + "photo": "/images/cast/photo_xxx.webp", + "bio": "Biography text", + "birthDate": "1990-01-01", + "birthPlace": "City, Country", + "ethnicity": "Asian", + "hair_color": "Black", + "eye_color": "Brown", + "height": 170, + "weight": 60, + "createdAt": "2024-01-01 00:00:00", + "updatedAt": "2024-01-01 00:00:00" + } +} +``` + +#### Create Adult Cast + +**Endpoint**: `POST /api/cast/adult` + +**Request Body**: +```json +{ + "name": "Actor Name", + "photo": "base64_encoded_image_or_url", + "bio": "Biography text", + "birthDate": "1990-01-01", + "birthPlace": "City, Country", + "ethnicity": "Asian", + "hair_color": "Black", + "eye_color": "Brown", + "height": 170, + "weight": 60 +} +``` + +**Response** (201 Created): +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +#### Update Adult Cast + +**Endpoint**: `PUT /api/cast/adult/:id` + +**Path Parameters**: +- `id` (integer, required) - Cast ID + +**Request Body**: Same as create, but all fields are optional. + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +#### Delete Adult Cast Specifics + +**Endpoint**: `DELETE /api/cast/adult/:id` + +**Path Parameters**: +- `id` (integer, required) - Cast ID + +**Note**: This only deletes the adult-specific attributes, not the cast member itself. + +**Response**: +```json +{ + "success": true, + "message": "Adult specifics deleted successfully" +} +``` + +--- + +### Images + +#### Serve Image + +**Endpoint**: `GET /api/images/*` + +**Description**: Serves images directly from the storage. Images are organized by type (movies, games, cast, etc.). + +**Example Requests**: +```bash +GET /api/images/movies/poster_xxx.webp +GET /api/images/cast/photo_xxx.webp +GET /api/images/episodes/poster_xxx.webp +``` + +**Response**: Binary image data (WebP format) + +--- + +### Settings + +#### Get Settings + +**Endpoint**: `GET /api/settings` + +**Description**: Retrieves application settings. + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1, + "key": "value", + "updatedAt": "2024-01-01 00:00:00" + } +} +``` + +#### Update Settings + +**Endpoint**: `PUT /api/settings` + +**Request Body**: +```json +{ + "key": "value" +} +``` + +**Response**: +```json +{ + "success": true, + "data": { + "id": 1, + "key": "value", + "updatedAt": "2024-01-01 00:00:00" + } +} +``` + +--- + +## Image Handling + +### Image Formats + +Images can be provided in two ways: + +1. **Base64-encoded string**: The API will automatically decode and save the image +2. **URL**: The API will store the URL as-is + +### Image Storage + +Images are stored in the following structure: +``` +/images/ +├── movies/ +│ ├── poster_*.webp +│ └── banner_*.webp +├── games/ +│ ├── poster_*.webp +│ └── banner_*.webp +├── cast/ +│ └── photo_*.webp +├── characters/ +│ └── char_*.webp +└── episodes/ + └── poster_*.webp +``` + +### Image Processing + +- All images are automatically converted to WebP format +- Old images are deleted when updating +- Images are served directly via the `/api/images/*` endpoint + +--- + +## Search & Filtering + +### Clean Names + +The API uses "clean names" for deduplication. A clean name is generated from the title/name by: +- Converting to lowercase +- Replacing special characters with hyphens +- Removing leading/trailing hyphens +- Replacing multiple consecutive hyphens with a single hyphen + +Example: "The Dark Knight" → "the-dark-knight" + +### Deduplication + +When creating media or cast members: +- The API checks if an item with the same clean name already exists +- If it exists, the existing ID is returned with a 200 status +- If it doesn't exist, a new item is created with a 201 status + +--- + +## Error Handling + +### Common Errors + +#### 400 Bad Request +```json +{ + "success": false, + "error": "Invalid JSON" +} +``` + +#### 404 Not Found +```json +{ + "success": false, + "error": "Media not found" +} +``` + +#### 405 Method Not Allowed +```json +{ + "success": false, + "error": "Method not allowed" +} +``` + +#### 500 Internal Server Error +```json +{ + "success": false, + "error": "Error message" +} +``` + +--- + +## CORS + +The API supports CORS with the following headers: + +``` +Access-Control-Allow-Origin: * +Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS +Access-Control-Allow-Headers: Content-Type +``` + +--- + +## Rate Limiting + +Currently, there is no rate limiting implemented. + +--- + +## Logging + +API logging can be enabled by setting the `API_LOGGING_ENABLED` environment variable to `true`. + +Logs are written to: +- API requests/responses: `/var/www/html/logs/api.log` +- PHP errors: `/var/www/html/logs/php_error.log` diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0ec190 --- /dev/null +++ b/README.md @@ -0,0 +1,331 @@ +# Kyoo Backend + +A RESTful API backend for media management, supporting movies, TV series, music albums, games, and cast information. + +## Features + +- **Media Management**: CRUD operations for movies, TV series, music albums, and games +- **Cast Management**: Manage actors, directors, and other staff with filmography tracking +- **Image Handling**: Automatic processing and storage of posters, banners, and cast photos +- **Search & Filtering**: Advanced search with pagination support +- **API Logging**: Built-in request/response logging for debugging +- **Docker Support**: Easy deployment with Docker Compose +- **Auto Documentation**: Self-documenting API endpoint + +## Tech Stack + +- **PHP 8.2** with Apache +- **MariaDB 10.11** +- **Docker & Docker Compose** +- **phpMyAdmin** for database management + +## Quick Start + +### Prerequisites + +- Docker installed +- Docker Compose installed + +### Installation + +1. Clone the repository: +```bash +git clone +cd kyoobackend +``` + +2. Start the services: +```bash +docker-compose up -d --build +``` + +3. Access the API: +- API Base URL: `http://localhost:6400/api` +- phpMyAdmin: `http://localhost:6402` + +### Database Credentials + +- **Host**: `mariadb` (within Docker) or `localhost:6401` (from host) +- **Database**: `kyoo` +- **User**: `kyoo_user` +- **Password**: `kyoo_password` +- **Root Password**: `root_password` + +## Project Structure + +``` +kyoobackend/ +├── api/ +│ ├── controllers/ # Request handlers +│ │ ├── MediaController.php +│ │ ├── CastController.php +│ │ ├── ImageController.php +│ │ └── SettingsController.php +│ ├── models/ # Data models +│ │ ├── Media.php +│ │ ├── Cast.php +│ │ ├── Game.php +│ │ ├── Series.php +│ │ ├── Music.php +│ │ └── ... +│ ├── services/ # Business logic +│ │ ├── ApiLogger.php +│ │ ├── ImageHandler.php +│ │ └── DocumentationService.php +│ ├── config.php # Configuration +│ ├── database.php # Database connection +│ ├── Router.php # API routing +│ └── index.php # Entry point +├── docker-compose.yml # Docker configuration +├── Dockerfile # PHP container build +└── php-custom.ini # PHP configuration +``` + +## API Endpoints + +### Root +- `GET /api` - API information and available endpoints + +### Documentation +- `GET /api/docs` - Auto-generated API documentation + +### Media +- `GET /api/media` - Get all media (with pagination and filtering) +- `GET /api/media/:id` - Get specific media item +- `POST /api/media` - Create new media +- `PUT /api/media/:id` - Update media +- `DELETE /api/media/:id` - Delete media + +### Series Episodes +- `GET /api/media/:id/episodes` - Get all episodes for a series +- `GET /api/media/:id/episodes/:episodeId` - Get specific episode +- `POST /api/media/:id/episodes` - Add episode to series +- `PUT /api/media/:id/episodes/:episodeId` - Update episode +- `DELETE /api/media/:id/episodes/:episodeId` - Delete episode + +### Music Tracks +- `GET /api/media/:id/tracks` - Get all tracks for an album +- `GET /api/media/:id/tracks/:trackId` - Get specific track +- `POST /api/media/:id/tracks` - Add track to album +- `PUT /api/media/:id/tracks/:trackId` - Update track +- `DELETE /api/media/:id/tracks/:trackId` - Delete track + +### Cast +- `GET /api/cast` - Get all cast members (with search) +- `GET /api/cast/:id` - Get specific cast member with filmography +- `GET /api/cast/:id/media` - Get all media for a cast member +- `POST /api/cast` - Create new cast member +- `PUT /api/cast/:id` - Update cast member +- `DELETE /api/cast/:id` - Delete cast member + +### Adult Cast +- `GET /api/cast/adult` - Get all adult cast members (with filters) +- `GET /api/cast/adult/:id` - Get specific adult cast member +- `POST /api/cast/adult` - Create new adult cast member +- `PUT /api/cast/adult/:id` - Update adult cast member +- `DELETE /api/cast/adult/:id` - Delete adult cast member + +### Images +- `GET /api/images/*` - Serve images directly + +### Settings +- `GET /api/settings` - Get application settings +- `PUT /api/settings` - Update settings + +## Query Parameters + +### Media List +- `page` - Page number (default: 1) +- `limit` - Items per page (default: 20) +- `category` - Filter by category (e.g., "movie", "tv", "music", "game") +- `type` - Filter by type (e.g., "Movie", "TV", "Album", "Game") +- `search` - Search in title and description + +### Cast List +- `page` - Page number (default: 1) +- `limit` - Items per page (default: 20) +- `search` - Search by name + +### Adult Cast List +- `page` - Page number (default: 1) +- `limit` - Items per page (default: 20) +- `search` - Search by name +- `ethnicity` - Filter by ethnicity +- `hair_color` - Filter by hair color + +### Series Episodes +- `season` - Filter by season number + +## Data Models + +### Media +```json +{ + "id": 1, + "title": "Movie Title", + "cleanname": "movie-title", + "year": 2024, + "poster": "/images/movies/poster_xxx.webp", + "banner": "/images/movies/banner_xxx.webp", + "description": "Description text", + "rating": 8.5, + "category": "movie", + "type": "Movie", + "status": "released", + "aspectRatio": "16:9", + "runtime": 120, + "director": "Director Name", + "writer": "Writer Name", + "source": "netflix", + "releaseDate": "2024-01-01", + "genres": ["Action", "Drama"], + "tags": ["tag1", "tag2"], + "studios": ["Studio1"], + "staff": [ + { + "id": 1, + "name": "Actor Name", + "role": "Actor", + "characterName": "Character", + "characterImage": "/images/characters/char_xxx.webp" + } + ], + "createdAt": "2024-01-01 00:00:00", + "updatedAt": "2024-01-01 00:00:00" +} +``` + +### Cast +```json +{ + "id": 1, + "name": "Actor Name", + "cleanname": "actor-name", + "photo": "/images/cast/photo_xxx.webp", + "bio": "Biography text", + "birthDate": "1990-01-01", + "birthPlace": "City, Country", + "occupations": ["Actor", "Director"], + "filmography": [ + { + "id": 1, + "title": "Movie Title", + "year": 2024, + "poster": "/images/movies/poster_xxx.webp", + "category": "movie", + "type": "Movie", + "role": "Actor", + "characterName": "Character" + } + ], + "createdAt": "2024-01-01 00:00:00", + "updatedAt": "2024-01-01 00:00:00" +} +``` + +## Response Format + +All responses follow this structure: + +### Success +```json +{ + "success": true, + "data": { ... } +} +``` + +### Error +```json +{ + "success": false, + "error": "Error message" +} +``` + +### Paginated List +```json +{ + "success": true, + "data": { + "items": [ ... ], + "total": 100, + "page": 1, + "limit": 20, + "totalPages": 5 + } +} +``` + +## Configuration + +Environment variables can be set in `docker-compose.yml` or as system environment variables: + +- `DB_HOST` - Database host (default: `mariadb`) +- `DB_NAME` - Database name (default: `kyoo`) +- `DB_USER` - Database user (default: `kyoo_user`) +- `DB_PASS` - Database password (default: `kyoo_password`) +- `API_LOGGING_ENABLED` - Enable API logging (default: `false`) + +## Docker Commands + +### Start services +```bash +docker-compose up -d --build +``` + +### Stop services +```bash +docker-compose down +``` + +### View logs +```bash +# All logs +docker-compose logs + +# Specific service +docker-compose logs php +docker-compose logs mariadb +``` + +### Reset database +```bash +docker-compose down -v +docker-compose up -d --build +``` + +## Development + +The API files are mounted as volumes, so changes are immediately reflected without rebuilding. + +### API Logging + +Logs are written to: +- API requests/responses: `/var/www/html/logs/api.log` +- PHP errors: `/var/www/html/logs/php_error.log` + +Enable logging by setting `API_LOGGING_ENABLED=true`. + +## Troubleshooting + +### PHP container won't start +```bash +docker-compose logs php +``` + +### Database connection errors +Check if MariaDB is running: +```bash +docker-compose ps +``` + +### Tables not created +Tables are auto-created on first API call. Check logs: +```bash +docker-compose logs php +``` + +## License + +[Add your license here]