Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for kobo sync and proxy #210

Merged
merged 20 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/symfony.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

name: Tests

on: [push,pull_request]
on:
push:
pull_request:
types: [opened, reopened]

permissions:
contents: read
Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@
"Composer\\Config::disableProcessTimeout",
"env XDEBUG_MODE=off ./vendor/bin/phpunit --colors=always"
],
"test-phpunit-xdebug": [
"Composer\\Config::disableProcessTimeout",
"env XDEBUG_MODE=debug XDEBUG_TRIGGER=1 ./vendor/bin/phpunit --colors=always"
],
"test": [
"@test-phpcs",
"@test-phpstan",
Expand Down
10 changes: 6 additions & 4 deletions config/packages/monolog.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
monolog:
channels:
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
- proxy # Proxy logs are logged in the dedicated "proxy" channel when it exists
- kobo
- kobo_proxy # Proxy logs are logged in the dedicated "proxy" channel when it exists
- kobo_http # All request done on AbstractKoboController
- kobo_sync # Log specific to Sync endpoint
- kobo_kepubify # kepub conversion


handlers:
Expand All @@ -16,10 +18,10 @@ monolog:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
proxy:
kobo:
type: rotating_file
level: debug
channels: ["proxy","kobo"]
channels: ["kobo_proxy","kobo_http", "kobo_sync", "kobo_kepubify"]
path: "%kernel.logs_dir%/kobo.%kernel.environment%.log"
max_files: 10

Expand Down
2 changes: 1 addition & 1 deletion config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ security:
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/api/v3/content/checkforchanges, roles: PUBLIC_ACCESS }
- { path: ^/api/v3/content/, roles: PUBLIC_ACCESS }
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/logout, roles: ROLE_USER }
- { path: ^/, roles: ROLE_USER }
Expand Down
7 changes: 7 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ parameters:
KOBO_IMAGE_API_URL: '%env(KOBO_IMAGE_API_URL)%'
env(KEPUBIFY_BIN): "/usr/bin/kepubify"
KEPUBIFY_BIN: '%env(KEPUBIFY_BIN)%'
KOBO_READINGSERVICES_URL: '%env(bool:KOBO_READINGSERVICES_URL)%'
env(KOBO_READINGSERVICES_URL): 'https://readingservices.kobo.com'
services:
# default configuration for services in *this* file
_defaults:
Expand Down Expand Up @@ -98,6 +100,7 @@ services:
App\Kobo\Proxy\KoboProxyConfiguration:
calls:
- [ setStoreApiUrl, ['%KOBO_API_URL%'] ]
- [ setReadingServiceUrl, ['%KOBO_READINGSERVICES_URL%'] ]
- [ setImageApiUrl, ['%KOBO_IMAGE_API_URL%'] ]
- [ setEnabled, ['%KOBO_PROXY_ENABLED%'] ]
- [ setUseProxyEverywhere, ['%KOBO_PROXY_USE_EVERYWHERE%'] ]
Expand All @@ -110,6 +113,10 @@ services:
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 101 }

App\Kobo\LogProcessor\KoboContextProcessor:
tags:
- { name: monolog.processor }

when@dev:
services:
Symfony\Component\HttpKernel\Profiler\Profiler: '@profiler'
Binary file added data/database.sqlite-journal
Binary file not shown.
29 changes: 29 additions & 0 deletions migrations/Version20241129185216.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241129185216 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add upstream_sync on koboDevice';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE kobo_device ADD upstream_sync TINYINT(1) DEFAULT 0 NOT NULL');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE kobo_device DROP upstream_sync');
}
}
2 changes: 1 addition & 1 deletion src/Controller/Kobo/AbstractKoboController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

use App\Controller\AbstractController;

class AbstractKoboController extends AbstractController
abstract class AbstractKoboController extends AbstractController
{
}
23 changes: 23 additions & 0 deletions src/Controller/Kobo/Api/ApiEndpointController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Controller\Kobo\Api;

use App\Controller\Kobo\AbstractKoboController;
use App\Kobo\Proxy\KoboStoreProxy;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/kobo/{accessKey}', name: 'kobo_')]
class ApiEndpointController extends AbstractKoboController
{
public function __construct(
protected KoboStoreProxy $koboStoreProxy,
) {
}

#[Route('/', name: 'api_endpoint')]
public function index(): Response
{
return new Response('<html><body>Hello Kobo</body></html>');
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?php

namespace App\Controller\Kobo;
namespace App\Controller\Kobo\Api;

use App\Controller\Kobo\AbstractKoboController;
use App\Entity\Book;
use App\Entity\KoboDevice;
use App\Kobo\DownloadHelper;
Expand All @@ -19,8 +20,8 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/kobo/{accessKey}', name: 'kobo')]
class KoboImageController extends AbstractKoboController
#[Route('/kobo/{accessKey}', name: 'kobo_')]
class ImageController extends AbstractKoboController
{
public function __construct(
protected KoboDeviceRepository $koboRepository,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?php

namespace App\Controller\Kobo;
namespace App\Controller\Kobo\Api\V1;

use App\Controller\Kobo\AbstractKoboController;
use App\Entity\KoboDevice;
use App\Kobo\Proxy\KoboProxyConfiguration;
use App\Kobo\Proxy\KoboStoreProxy;
Expand All @@ -14,8 +15,8 @@
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/kobo/{accessKey}', name: 'kobo')]
class KoboAnalyticsController extends AbstractKoboController
#[Route('/kobo/{accessKey}/v1/analytics', name: 'kobo_')]
class AnalyticsController extends AbstractKoboController
{
public function __construct(
protected KoboProxyConfiguration $koboProxyConfiguration,
Expand All @@ -30,7 +31,7 @@ public function __construct(
* @throws GuzzleException
* @throws \JsonException
*/
#[Route('/v1/analytics/gettests', methods: ['POST', 'GET'])]
#[Route('/gettests', methods: ['POST', 'GET'])]
public function analyticsTests(Request $request): Response
{
$content = $request->getContent();
Expand All @@ -50,7 +51,7 @@ public function analyticsTests(Request $request): Response
* @throws \JsonException
* @throws GuzzleException
*/
#[Route('/v1/analytics/event', methods: ['POST'])]
#[Route('/event', methods: ['POST'])]
public function analyticsEvent(Request $request, KoboDevice $kobo): Response
{
// Save the device_id and model
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
<?php

namespace App\Controller\Kobo;
namespace App\Controller\Kobo\Api\V1;

use App\Controller\Kobo\AbstractKoboController;
use App\Entity\Book;
use App\Entity\KoboDevice;
use App\Kobo\DownloadHelper;
use App\Kobo\Proxy\KoboStoreProxy;
use App\Repository\BookRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

#[Route('/kobo/{accessKey}', name: 'app_kobo')]
class KoboDownloadController extends AbstractKoboController
#[Route('/kobo/{accessKey}/v1/download', name: 'kobo_')]
class DownloadController extends AbstractKoboController
{
public function __construct(
protected BookRepository $bookRepository,
protected DownloadHelper $downloadHelper,
protected KoboStoreProxy $koboStoreProxy,
protected LoggerInterface $logger)
{
) {
}

#[Route('/v1/download/{id}.{extension}', name: 'download', requirements: ['bookId' => '\d+', 'extension' => '[A-Za-z0-9]+'], methods: ['GET'])]
#[Route('/{id}.{extension}', name: 'download', requirements: ['bookId' => '\d+', 'extension' => '[A-Za-z0-9]+'], methods: ['GET'])]
public function download(KoboDevice $kobo, Book $book, string $extension): Response
{
$this->assertCanDownload($kobo, $book);
Expand Down
70 changes: 70 additions & 0 deletions src/Controller/Kobo/Api/V1/GenericController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace App\Controller\Kobo\Api\V1;

use App\Controller\Kobo\AbstractKoboController;
use App\Kobo\Proxy\KoboStoreProxy;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/kobo/{accessKey}/v1', name: 'kobo_')]
class GenericController extends AbstractKoboController
{
public function __construct(
protected KoboStoreProxy $koboStoreProxy,
) {
}

/**
* @throws GuzzleException
*/
#[Route('/user/loyalty/benefits')]
public function benefits(Request $request): Response
{
if ($this->koboStoreProxy->isEnabled()) {
return $this->koboStoreProxy->proxyOrRedirect($request);
}

return new JsonResponse(['Benefits' => new \stdClass()]);
}

/**
* @throws GuzzleException
*/
#[Route('/affiliate', methods: ['GET', 'POST'])] // ?PlatformID=00000000-0000-0000-0000-000000000384&SerialNumber=xxxxxxx
#[Route('/assets', methods: ['GET'])]
#[Route('/deals', methods: ['GET', 'POST'])]
#[Route('/products', methods: ['GET', 'POST'])]
#[Route('/products/books/external/{uuid}', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/products/books/series/{uuid}', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/products/books/{uuid}', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/products/books/{uuid}/', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/products/books/{uuid}/access', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/products/dailydeal', methods: ['GET', 'POST'])]
#[Route('/products/deals', methods: ['GET', 'POST'])]
#[Route('/products/featured/', methods: ['GET', 'POST'])]
#[Route('/products/featured/{uuid}', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/products/{uuid}/prices', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/products/{uuid}/recommendations', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/user/recommendations/feedback', methods: ['GET', 'POST'])]
#[Route('/products/{uuid}/reviews', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/user/profile')]
#[Route('/configuration')]
#[Route('/auth/device')]
#[Route('/auth/refresh')]
#[Route('/library/borrow')]
#[Route('/auth/exchange')]
#[Route('/library/{uuid}', methods: ['DELETE'])]
#[Route('/user/recommendations', requirements: ['uuid' => '^[a-zA-Z0-9\-]+$'], methods: ['GET', 'POST'])]
#[Route('/user/wishlist')] // ?PageSize=100&PageIndex=0
public function proxy(Request $request, LoggerInterface $koboLogger): Response
{
$koboLogger->info('Kobo API Proxy request on '.$request->getPathInfo(), ['request' => $request->getContent(), 'headers' => $request->headers->all()]);

return $this->koboStoreProxy->proxyOrRedirect($request);
}
}
Loading