mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
Stuff i guess ?
This commit is contained in:
51
app/Controllers/Api/AuthController.php
Normal file
51
app/Controllers/Api/AuthController.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Controllers\Controller;
|
||||
use App\Services\AuthService;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
private AuthService $authService;
|
||||
|
||||
public function __construct(AuthService $authService)
|
||||
{
|
||||
$this->authService = $authService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is authenticated (API endpoint)
|
||||
*/
|
||||
public function checkAuth(Request $request, Response $response, $args)
|
||||
{
|
||||
try {
|
||||
if (!$this->authService->isLoggedIn()) {
|
||||
return $this->jsonResponse($response->withStatus(401), [
|
||||
'error' => '401 Forbidden'
|
||||
]);
|
||||
}
|
||||
|
||||
$user = $this->authService->getCurrentUser();
|
||||
if (!$user) {
|
||||
return $this->jsonResponse($response->withStatus(401), [
|
||||
'error' => '401 Forbidden'
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'is_admin' => $this->authService->isAdmin()
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => 'Authentication check failed'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
569
app/Controllers/Api/PlayniteController.php
Normal file
569
app/Controllers/Api/PlayniteController.php
Normal file
@@ -0,0 +1,569 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Controllers\Controller;
|
||||
use App\Models\Game;
|
||||
use App\Services\PlayniteImportService;
|
||||
|
||||
class PlayniteController extends Controller
|
||||
{
|
||||
|
||||
private \PDO $pdo;
|
||||
private PlayniteImportService $importService;
|
||||
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
$this->importService = new PlayniteImportService($pdo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/playnite/insert",
|
||||
* summary="Insert or update games from Playnite",
|
||||
* tags={"Playnite"},
|
||||
* operationId="insertGames",
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"games"},
|
||||
* @OA\Property(
|
||||
* property="games",
|
||||
* type="array",
|
||||
* @OA\Items(type="object")
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="update_existing",
|
||||
* type="boolean",
|
||||
* default=true,
|
||||
* description="Whether to update existing games"
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Games successfully imported/updated",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="result", type="object")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=400,
|
||||
* description="Invalid input"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=500,
|
||||
* description="Server error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @return Response
|
||||
*/
|
||||
public function insertGames(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (!isset($data['games']) || !is_array($data['games'])) {
|
||||
return $this->jsonResponse($response->withStatus(400), [
|
||||
'error' => 'Games data is required'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$importResult = $this->importService->importGames($data['games'], true);
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'success' => true,
|
||||
'result' => $importResult
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/playnite/update",
|
||||
* summary="Update existing games from Playnite",
|
||||
* tags={"Playnite"},
|
||||
* operationId="updateGames",
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"games"},
|
||||
* @OA\Property(
|
||||
* property="games",
|
||||
* type="array",
|
||||
* @OA\Items(type="object")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Games successfully updated",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="result", type="object")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=400,
|
||||
* description="Invalid input"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=500,
|
||||
* description="Server error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @return Response
|
||||
*/
|
||||
public function updateGames(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (!isset($data['games']) || !is_array($data['games'])) {
|
||||
return $this->jsonResponse($response->withStatus(400), [
|
||||
'error' => 'Games data is required'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$importResult = $this->importService->importGames($data['games'], true);
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'success' => true,
|
||||
'result' => $importResult
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/playnite/delete",
|
||||
* summary="Delete games from Playnite",
|
||||
* tags={"Playnite"},
|
||||
* operationId="deleteGames",
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"games"},
|
||||
* @OA\Property(
|
||||
* property="games",
|
||||
* type="array",
|
||||
* @OA\Items(type="object")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Games successfully deleted",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="result", type="object",
|
||||
* @OA\Property(property="deleted", type="integer"),
|
||||
* @OA\Property(property="errors", type="array", @OA\Items(type="string"))
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=400,
|
||||
* description="Invalid input"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=500,
|
||||
* description="Server error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteGames(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (!isset($data['games']) || !is_array($data['games'])) {
|
||||
return $this->jsonResponse($response->withStatus(400), [
|
||||
'error' => 'Games data is required'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$results = [
|
||||
'deleted' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
foreach ($data['games'] as $gameData) {
|
||||
try {
|
||||
// Find the game by platform_game_id and source_id
|
||||
$existingGame = $this->findExistingGame($gameData);
|
||||
|
||||
if ($existingGame) {
|
||||
$this->deleteGame($existingGame['id']);
|
||||
$results['deleted']++;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$results['errors'][] = "Failed to delete {$gameData['title']}: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'success' => true,
|
||||
'result' => $results
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/playnite/upload-images",
|
||||
* summary="Upload game images from Playnite",
|
||||
* tags={"Playnite"},
|
||||
* operationId="uploadImages",
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* oneOf={
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="name", type="string"),
|
||||
* @OA\Property(property="cover", type="string", format="byte"),
|
||||
* @OA\Property(property="icon", type="string", format="byte"),
|
||||
* @OA\Property(property="background", type="string", format="byte")
|
||||
* ),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="games",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="name", type="string"),
|
||||
* @OA\Property(property="cover", type="string", format="byte"),
|
||||
* @OA\Property(property="icon", type="string", format="byte"),
|
||||
* @OA\Property(property="background", type="string", format="byte")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Images successfully uploaded",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="result", type="object",
|
||||
* @OA\Property(property="uploaded", type="integer"),
|
||||
* @OA\Property(property="errors", type="array", @OA\Items(type="string"))
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=500,
|
||||
* description="Server error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @return Response
|
||||
*/
|
||||
public function uploadImages(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
try {
|
||||
$results = [
|
||||
'uploaded' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
// Handle image uploads based on the format expected by the plugin
|
||||
if (isset($data['name']) && isset($data['cover'])) {
|
||||
// Single game image upload
|
||||
$result = $this->handleImageUpload($data);
|
||||
if ($result) {
|
||||
$results['uploaded']++;
|
||||
}
|
||||
} elseif (isset($data['games']) && is_array($data['games'])) {
|
||||
// Multiple games with images
|
||||
foreach ($data['games'] as $gameData) {
|
||||
$result = $this->handleImageUpload($gameData);
|
||||
if ($result) {
|
||||
$results['uploaded']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->jsonResponse($response, [
|
||||
'success' => true,
|
||||
'result' => $results
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->jsonResponse($response->withStatus(500), [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle individual image upload
|
||||
*/
|
||||
private function handleImageUpload(array $gameData): bool
|
||||
{
|
||||
try {
|
||||
// For now, we'll just validate the data format
|
||||
// In a real implementation, you might want to save the images to disk
|
||||
// and update the game records with the image paths
|
||||
|
||||
$name = $gameData['name'] ?? '';
|
||||
$cover = $gameData['cover'] ?? '';
|
||||
$icon = $gameData['icon'] ?? '';
|
||||
$background = $gameData['background'] ?? '';
|
||||
|
||||
// Validate base64 images
|
||||
if ($cover && !preg_match('/^data:image\/(jpeg|png|gif|webp);base64,/', $cover)) {
|
||||
throw new \Exception("Invalid cover image format");
|
||||
}
|
||||
|
||||
// Here you would typically:
|
||||
// 1. Decode base64 images
|
||||
// 2. Save them to the filesystem
|
||||
// 3. Update the game record with the image paths
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
error_log("Image upload failed for game {$name}: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find existing game by platform_game_id and source_id
|
||||
*/
|
||||
private function findExistingGame(array $gameData): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT id, title, platform_game_id, source_id
|
||||
FROM games
|
||||
WHERE platform_game_id = :platform_game_id AND source_id = :source_id
|
||||
");
|
||||
$stmt->execute([
|
||||
'platform_game_id' => $gameData['platform_game_id'],
|
||||
'source_id' => $gameData['source_id']
|
||||
]);
|
||||
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete game
|
||||
*/
|
||||
private function deleteGame(int $gameId): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare("DELETE FROM games WHERE id = :id");
|
||||
$stmt->execute(['id' => $gameId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform Playnite game data to internal format
|
||||
*/
|
||||
private function transformPlayniteGame(array $game): array
|
||||
{
|
||||
// Find or create source
|
||||
$source = $this->findOrCreateSource($game);
|
||||
|
||||
// Transform the game data similar to PlayniteImportService
|
||||
return [
|
||||
'title' => $game['Name'] ?? $game['name'] ?? '',
|
||||
'game_key' => $this->generateGameKey($game['Name'] ?? $game['name'] ?? ''),
|
||||
'description' => $this->cleanHtml($game['Description'] ?? $game['description'] ?? ''),
|
||||
'platform_game_id' => $game['GameId'] ?? $game['game_id'] ?? '',
|
||||
'platform' => $this->extractPlatformFromPlaynite($game),
|
||||
'source_id' => $source['id'],
|
||||
|
||||
// Rich media
|
||||
'background_image' => $game['BackgroundImage'] ?? $game['background'] ?? null,
|
||||
'cover_image' => $game['CoverImage'] ?? $game['cover'] ?? null,
|
||||
'icon' => $game['Icon'] ?? $game['icon'] ?? null,
|
||||
|
||||
// Play statistics
|
||||
'playtime_minutes' => $this->parsePlaytime($game['Playtime'] ?? $game['playtime'] ?? 0),
|
||||
'play_count' => $game['PlayCount'] ?? $game['play_count'] ?? 0,
|
||||
|
||||
// Enhanced ratings
|
||||
'rating' => $this->normalizeRating($game['CriticScore'] ?? $game['critic_score'] ?? null),
|
||||
'critic_score' => $game['CriticScore'] ?? $game['critic_score'] ?? null,
|
||||
'community_score' => $game['CommunityScore'] ?? $game['community_score'] ?? null,
|
||||
'user_score' => $game['UserScore'] ?? $game['user_score'] ?? null,
|
||||
|
||||
// Timestamps
|
||||
'added_at' => isset($game['Added']) ? date('Y-m-d H:i:s', strtotime($game['Added'])) : null,
|
||||
'modified_at' => isset($game['Modified']) ? date('Y-m-d H:i:s', strtotime($game['Modified'])) : null,
|
||||
'last_played_at' => isset($game['LastActivity']) ? date('Y-m-d H:i:s', strtotime($game['LastActivity'])) : null,
|
||||
|
||||
// Playnite metadata
|
||||
'metadata' => json_encode([
|
||||
'playnite_id' => $game['Id'] ?? $game['playnite_id'] ?? null,
|
||||
'version' => $game['Version'] ?? $game['version'] ?? null,
|
||||
'hidden' => $this->toBoolean($game['Hidden'] ?? $game['hidden'] ?? false),
|
||||
'notes' => $game['Notes'] ?? $game['notes'] ?? null,
|
||||
'manual' => $game['Manual'] ?? $game['manual'] ?? null,
|
||||
'pre_script' => $game['PreScript'] ?? $game['pre_script'] ?? null,
|
||||
'post_script' => $game['PostScript'] ?? $game['post_script'] ?? null,
|
||||
'game_started_script' => $game['GameStartedScript'] ?? $game['game_started_script'] ?? null,
|
||||
'use_global_scripts' => [
|
||||
'pre' => $this->toBoolean($game['UseGlobalPreScript'] ?? $game['use_global_pre_script'] ?? true),
|
||||
'post' => $this->toBoolean($game['UseGlobalPostScript'] ?? $game['use_global_post_script'] ?? true),
|
||||
'game_started' => $this->toBoolean($game['UseGlobalGameStartedScript'] ?? $game['use_global_game_started_script'] ?? true)
|
||||
]
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or create a source for the game
|
||||
*/
|
||||
private function findOrCreateSource(array $game): array
|
||||
{
|
||||
$sourceName = $game['Source']['Name'] ?? $game['source'] ?? 'Playnite';
|
||||
$sourceId = $game['Source']['Id'] ?? $game['source_id'] ?? null;
|
||||
|
||||
// Try to find existing source
|
||||
$stmt = $this->pdo->prepare("SELECT id, display_name FROM sources WHERE display_name = :name OR id = :source_id");
|
||||
$stmt->execute([
|
||||
'name' => $sourceName,
|
||||
'source_id' => $sourceId
|
||||
]);
|
||||
|
||||
$source = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$source) {
|
||||
// Create new source
|
||||
$stmt = $this->pdo->prepare("INSERT INTO sources (display_name, created_at, updated_at) VALUES (:name, NOW(), NOW())");
|
||||
$stmt->execute(['name' => $sourceName]);
|
||||
$source = ['id' => $this->pdo->lastInsertId(), 'display_name' => $sourceName];
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a consistent game key for grouping
|
||||
*/
|
||||
private function generateGameKey(string $title): string
|
||||
{
|
||||
return \App\Models\Game::generateGameKey($title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract platform from Playnite data
|
||||
*/
|
||||
private function extractPlatformFromPlaynite(array $game): string
|
||||
{
|
||||
if (isset($game['Platforms']) && is_array($game['Platforms'])) {
|
||||
$platformNames = array_map(function($platform) {
|
||||
return $platform['Name'] ?? 'Unknown';
|
||||
}, $game['Platforms']);
|
||||
|
||||
return implode(', ', $platformNames);
|
||||
}
|
||||
|
||||
if (isset($game['Platform']) && is_array($game['Platform'])) {
|
||||
return $game['Platform']['Name'] ?? 'PC';
|
||||
}
|
||||
|
||||
return $game['platform'] ?? 'PC';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse playtime from Playnite format (usually in seconds)
|
||||
*/
|
||||
private function parsePlaytime($playtime): int
|
||||
{
|
||||
if (is_numeric($playtime)) {
|
||||
return (int)($playtime / 60); // Convert seconds to minutes
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize rating to 0-10 scale
|
||||
*/
|
||||
private function normalizeRating($rating): ?float
|
||||
{
|
||||
if (is_numeric($rating)) {
|
||||
$rating = (float)$rating;
|
||||
// If rating is 0-100 scale, convert to 0-10
|
||||
if ($rating > 10) {
|
||||
return $rating / 10;
|
||||
}
|
||||
return $rating;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean HTML from description
|
||||
*/
|
||||
private function cleanHtml(?string $html): ?string
|
||||
{
|
||||
if (!$html) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove HTML tags but keep basic formatting
|
||||
$text = strip_tags($html);
|
||||
// Decode HTML entities
|
||||
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
// Clean up extra whitespace
|
||||
$text = preg_replace('/\s+/', ' ', $text);
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to boolean
|
||||
*/
|
||||
private function toBoolean($value): bool
|
||||
{
|
||||
if ($value === null || $value === false || $value === 0 || $value === '0') {
|
||||
return false;
|
||||
}
|
||||
if ($value === true || $value === 1 || $value === '1') {
|
||||
return true;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
return !empty(trim($value));
|
||||
}
|
||||
return (bool) $value;
|
||||
}
|
||||
}
|
||||
41
app/Controllers/Api/_openapi.php
Normal file
41
app/Controllers/Api/_openapi.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @OA\OpenApi(
|
||||
* @OA\Info(
|
||||
* title="Playnite API",
|
||||
* version="1.0.0",
|
||||
* description="API for managing games and media from Playnite",
|
||||
* @OA\Contact(
|
||||
* email="support@example.com"
|
||||
* ),
|
||||
* @OA\License(
|
||||
* name="Apache 2.0",
|
||||
* url="http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
* )
|
||||
* ),
|
||||
* @OA\Server(
|
||||
* url="/api",
|
||||
* description="API Server"
|
||||
* ),
|
||||
* @OA\Components(
|
||||
* @OA\Schema(
|
||||
* schema="Error",
|
||||
* type="object",
|
||||
* @OA\Property(property="success", type="boolean", example=false),
|
||||
* @OA\Property(property="error", type="string", example="Error message")
|
||||
* ),
|
||||
* @OA\Schema(
|
||||
* schema="Success",
|
||||
* type="object",
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="result", type="object")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Tag(
|
||||
* name="Playnite",
|
||||
* description="Endpoints for Playnite integration"
|
||||
* )
|
||||
*/
|
||||
Reference in New Issue
Block a user