parse EpisodeAction message from AntennaPod
persist and list with database create episodeAction list reponse (with mocked timestamp, started and total) create episodeActions with received values update existing episodeActions by unique episode link receive and store subscription changes deal with multiple subscription changes in single request split database into subdirectories only return subscription changes younger then passed parameter since parse passed timestamp parse passed timestamp for episode_actions listing only return list of urls for subscription changes align list endpoint naming schema store userId with episode actions and subscriptions return json object on application root route
This commit is contained in:
commit
72bf365285
|
@ -0,0 +1 @@
|
|||
vendor/*
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||
<id>gpoddersync</id>
|
||||
<name>G Podder Sync</name>
|
||||
<summary>replicate GPodder API</summary>
|
||||
<description><![CDATA[Expose GPodder API to sync podcast consumer apps like AntennaPod]]></description>
|
||||
<version>0.0.1</version>
|
||||
<licence>agpl</licence>
|
||||
<author mail="thrillfall@disroot.org" >Thrillfall</author>
|
||||
<namespace>GPodderSync</namespace>
|
||||
<category>integration</category>
|
||||
<bugs>https://github.com/thrillfall</bugs>
|
||||
<dependencies>
|
||||
<nextcloud min-version="15" max-version="15"/>
|
||||
</dependencies>
|
||||
<navigations>
|
||||
<navigation>
|
||||
<name>G Podder Sync</name>
|
||||
<route>gpoddersync.page.index</route>
|
||||
</navigation>
|
||||
</navigations>
|
||||
</info>
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
/**
|
||||
* Create your routes in here. The name is the lowercase name of the controller
|
||||
* without the controller part, the stuff after the hash is the method.
|
||||
* e.g. page#index -> OCA\GPodderSync\Controller\PageController->index()
|
||||
*
|
||||
* The controller class has to be registered in the application.php file since
|
||||
* it's instantiated in there
|
||||
*/
|
||||
return [
|
||||
'routes' => [
|
||||
['name' => 'episode_action#create', 'url' => '/episode_action/create', 'verb' => 'POST'],
|
||||
['name' => 'episode_action#list', 'url' => '/episode_action', 'verb' => 'GET'],
|
||||
|
||||
['name' => 'subscription_change#list', 'url' => '/subscriptions', 'verb' => 'GET'],
|
||||
['name' => 'subscription_change#create', 'url' => '/subscription_change/create', 'verb' => 'POST'],
|
||||
|
||||
['name' => 'version#index', 'url' => '/', 'verb' => 'GET'],
|
||||
|
||||
|
||||
|
||||
]
|
||||
];
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Controller;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionReader;
|
||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity;
|
||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
|
||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\IRequest;
|
||||
|
||||
class EpisodeActionController extends Controller {
|
||||
|
||||
/**
|
||||
* @var EpisodeActionRepository
|
||||
*/
|
||||
private EpisodeActionRepository $episodeActionRepository;
|
||||
/**
|
||||
* @var EpisodeActionWriter
|
||||
*/
|
||||
private EpisodeActionWriter $episodeActionWriter;
|
||||
/**
|
||||
* @var EpisodeActionReader
|
||||
*/
|
||||
private EpisodeActionReader $episodeActionReader;
|
||||
private $userId;
|
||||
|
||||
public function __construct(
|
||||
string $AppName,
|
||||
IRequest $request,
|
||||
$UserId,
|
||||
EpisodeActionRepository $episodeActionRepository,
|
||||
EpisodeActionWriter $episodeActionWriter,
|
||||
EpisodeActionReader $episodeActionReader
|
||||
) {
|
||||
parent::__construct($AppName, $request);
|
||||
$this->episodeActionRepository = $episodeActionRepository;
|
||||
$this->episodeActionWriter = $episodeActionWriter;
|
||||
$this->episodeActionReader = $episodeActionReader;
|
||||
$this->userId = $UserId;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function create($data) {
|
||||
$episodeAction = $this->episodeActionReader->fromString($data);
|
||||
$episodeActionEntity = new EpisodeActionEntity();
|
||||
$episodeActionEntity->setPodcast($episodeAction->getPodcast());
|
||||
$episodeActionEntity->setEpisode($episodeAction->getEpisode());
|
||||
$episodeActionEntity->setAction($episodeAction->getAction());
|
||||
$episodeActionEntity->setPosition($episodeAction->getPosition());
|
||||
$episodeActionEntity->setStarted($episodeAction->getStarted());
|
||||
$episodeActionEntity->setTotal($episodeAction->getTotal());
|
||||
$episodeActionEntity->setTimestamp($episodeAction->getTimestamp());
|
||||
$episodeActionEntity->setUserId($this->userId);
|
||||
|
||||
try {
|
||||
return $this->episodeActionWriter->save($episodeActionEntity);
|
||||
} catch (UniqueConstraintViolationException $ex) {
|
||||
$IdEpisodeActionEntityToUpdate = $this->episodeActionRepository->findByEpisode($episodeAction->getEpisode(), $this->userId)->getId();
|
||||
$episodeActionEntity->setId($IdEpisodeActionEntityToUpdate);
|
||||
return $this->episodeActionWriter->update($episodeActionEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* @param int $since
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function list(int $since): JSONResponse {
|
||||
$sinceDatetime = $this->createDateTimeFromTimestamp($since);
|
||||
return new JSONResponse([
|
||||
"actions" => $this->episodeActionRepository->findAll($sinceDatetime, $this->userId),
|
||||
"timestamp" => time()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $since
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
private function createDateTimeFromTimestamp(?int $since): DateTime {
|
||||
return ($since)
|
||||
? (new \DateTime)->setTimestamp($since)
|
||||
: (new \DateTime('-1 week'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Controller;
|
||||
|
||||
use DateTime;
|
||||
use OCA\GPodderSync\Core\SubscriptionChange\SubscriptionChangeSaver;
|
||||
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeEntity;
|
||||
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeRepository;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\IRequest;
|
||||
|
||||
class SubscriptionChangeController extends Controller {
|
||||
|
||||
private string $AppName;
|
||||
/**
|
||||
* @var SubscriptionChangeSaver
|
||||
*/
|
||||
private SubscriptionChangeSaver $subscriptionChangeSaver;
|
||||
/**
|
||||
* @var SubscriptionChangeRepository
|
||||
*/
|
||||
private SubscriptionChangeRepository $subscriptionChangeRepository;
|
||||
private $userId;
|
||||
|
||||
public function __construct(
|
||||
string $AppName,
|
||||
IRequest $request,
|
||||
$UserId,
|
||||
SubscriptionChangeSaver $subscriptionChangeSaver,
|
||||
SubscriptionChangeRepository $subscriptionChangeRepository
|
||||
|
||||
) {
|
||||
parent::__construct($AppName, $request);
|
||||
$this->subscriptionChangeSaver = $subscriptionChangeSaver;
|
||||
$this->subscriptionChangeRepository = $subscriptionChangeRepository;
|
||||
$this->userId = $UserId;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function create($add, $remove) {
|
||||
return $this->subscriptionChangeSaver->saveSubscriptionChanges($add, $remove, $this->userId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* @param int $since
|
||||
* @return JSONResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function list(int $since = null): JSONResponse {
|
||||
$sinceDatetime = $this->createDateTimeFromTimestamp($since);
|
||||
return new JSONResponse([
|
||||
"add" => $this->extractUrlList($this->subscriptionChangeRepository->findAllSubscribed($sinceDatetime, $this->userId)),
|
||||
"remove" => $this->extractUrlList($this->subscriptionChangeRepository->findAllUnSubscribed($sinceDatetime, $this->userId)),
|
||||
"timestamp" => time()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $since
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
private function createDateTimeFromTimestamp(?int $since): DateTime {
|
||||
return ($since)
|
||||
? (new \DateTime)->setTimestamp($since)
|
||||
: (new \DateTime('-1 week'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $allSubscribed
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function extractUrlList(array $allSubscribed): array {
|
||||
return array_map(function (SubscriptionChangeEntity $subscription) {
|
||||
return $subscription->getUrl();
|
||||
}, $allSubscribed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Controller;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionReader;
|
||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity;
|
||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
|
||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\IRequest;
|
||||
|
||||
class VersionController extends Controller {
|
||||
|
||||
public function __construct(
|
||||
string $AppName,
|
||||
IRequest $request,
|
||||
$UserId
|
||||
) {
|
||||
parent::__construct($AppName, $request);
|
||||
$this->userId = $UserId;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index() {
|
||||
return new JSONResponse(["version" => "0.1"]);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Core\EpisodeAction;
|
||||
|
||||
class EpisodeAction {
|
||||
private string $podcast;
|
||||
private string $episode;
|
||||
private string $action;
|
||||
private string $timestamp;
|
||||
private int $started;
|
||||
private int $position;
|
||||
private int $total;
|
||||
|
||||
public function __construct(
|
||||
string $podcast,
|
||||
string $episode,
|
||||
string $action,
|
||||
string $timestamp,
|
||||
int $started,
|
||||
int $position,
|
||||
int $total
|
||||
) {
|
||||
$this->podcast = $podcast;
|
||||
$this->episode = $episode;
|
||||
$this->action = $action;
|
||||
$this->timestamp = $timestamp;
|
||||
$this->started = $started;
|
||||
$this->position = $position;
|
||||
$this->total = $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPodcast(): string {
|
||||
return $this->podcast;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEpisode(): string {
|
||||
return $this->episode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAction(): string {
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTimestamp(): string {
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getStarted(): int {
|
||||
return $this->started;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPosition(): int {
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTotal(): int {
|
||||
return $this->total;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Core\EpisodeAction;
|
||||
|
||||
class EpisodeActionReader {
|
||||
public function fromString(string $episodeActionString): EpisodeAction {
|
||||
file_put_contents('actionreader.log', var_export($episodeActionString, true), FILE_APPEND);
|
||||
preg_match(
|
||||
'/\[EpisodeAction{(podcast=\')(?<podcast>.*?)(\', episode=\')(?<episode>.*?)(\', action=)(?<action>.*?)(, timestamp=)(?<timestamp>.*?)(, started=)(?<started>.*?)(, position=)(?<position>.*?)(, total=)(?<total>.*?)}]*/',
|
||||
$episodeActionString,
|
||||
$matches
|
||||
);
|
||||
|
||||
file_put_contents('actionreader.log', var_export($matches, true), FILE_APPEND);
|
||||
|
||||
return new EpisodeAction(
|
||||
$matches["podcast"],
|
||||
$matches["episode"],
|
||||
$matches["action"],
|
||||
$matches["timestamp"],
|
||||
(int)$matches["started"],
|
||||
(int)$matches["position"],
|
||||
(int)$matches["total"],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Core\SubscriptionChange;
|
||||
|
||||
class SubscriptionChange {
|
||||
private string $url;
|
||||
private bool $isSubscribed;
|
||||
|
||||
public function __construct(
|
||||
string $url,
|
||||
bool $isSubscribed
|
||||
) {
|
||||
$this->url = $url;
|
||||
$this->isSubscribed = $isSubscribed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSubscribed(): bool {
|
||||
return $this->isSubscribed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl(): string {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function __toString() : String {
|
||||
return $this->url;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Core\SubscriptionChange;
|
||||
|
||||
class SubscriptionChangeRequestParser {
|
||||
|
||||
/**
|
||||
* @var SubscriptionChangesReader
|
||||
*/
|
||||
private SubscriptionChangesReader $subscriptionChangeReader;
|
||||
|
||||
public function __construct(SubscriptionChangesReader $subscriptionChangeReader) {
|
||||
$this->subscriptionChangeReader = $subscriptionChangeReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $urlsSubscribed
|
||||
* @param string $urlsUnsubscribed
|
||||
*
|
||||
* @return SubscriptionChange[]
|
||||
*/
|
||||
public function createSubscriptionChangeList(string $urlsSubscribed, string $urlsUnsubscribed): array {
|
||||
$urlsToSubscribe = $this->subscriptionChangeReader->fromString($urlsSubscribed, true);
|
||||
$urlsToDelete = $this->subscriptionChangeReader->fromString($urlsUnsubscribed, false);
|
||||
|
||||
/** @var \OCA\GPodderSync\Core\SubscriptionChange\SubscriptionChange[] $subscriptionChanges */
|
||||
return array_merge($urlsToSubscribe, $urlsToDelete);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Core\SubscriptionChange;
|
||||
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeEntity;
|
||||
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeRepository;
|
||||
use OCA\GPodderSync\Db\SubscriptionChange\SubscriptionChangeWriter;
|
||||
|
||||
class SubscriptionChangeSaver {
|
||||
|
||||
/**
|
||||
* @var SubscriptionChangesReader
|
||||
*/
|
||||
private SubscriptionChangesReader $subscriptionChangeReader;
|
||||
/**
|
||||
* @var SubscriptionChangeRepository
|
||||
*/
|
||||
private SubscriptionChangeRepository $subscriptionChangeRepository;
|
||||
/**
|
||||
* @var SubscriptionChangeWriter
|
||||
*/
|
||||
private SubscriptionChangeWriter $subscriptionChangeWriter;
|
||||
/**
|
||||
* @var SubscriptionChangeRequestParser
|
||||
*/
|
||||
private SubscriptionChangeRequestParser $subscriptionChangeRequestParser;
|
||||
|
||||
public function __construct(
|
||||
SubscriptionChangeRequestParser $subscriptionChangeRequestParser,
|
||||
SubscriptionChangeRepository $subscriptionChangeRepository,
|
||||
SubscriptionChangeWriter $subscriptionChangeWriter
|
||||
) {
|
||||
$this->subscriptionChangeRepository = $subscriptionChangeRepository;
|
||||
$this->subscriptionChangeWriter = $subscriptionChangeWriter;
|
||||
$this->subscriptionChangeRequestParser = $subscriptionChangeRequestParser;
|
||||
}
|
||||
|
||||
public function saveSubscriptionChanges(string $urlsSubscribed, string $urlsUnsubscribed, string $userId) : void {
|
||||
$subscriptionChanges = $this->subscriptionChangeRequestParser->createSubscriptionChangeList($urlsSubscribed, $urlsUnsubscribed);
|
||||
foreach ($subscriptionChanges as $urlChangedSubscriptionStatus) {
|
||||
$subscriptionChangeEntity = new SubscriptionChangeEntity();
|
||||
$subscriptionChangeEntity->setUrl($urlChangedSubscriptionStatus->getUrl());
|
||||
$subscriptionChangeEntity->setSubscribed($urlChangedSubscriptionStatus->isSubscribed());
|
||||
$subscriptionChangeEntity->setUpdated((new \DateTime())->format("Y-m-d\TH:i:s"));
|
||||
$subscriptionChangeEntity->setUserId($userId);
|
||||
|
||||
try {
|
||||
$this->subscriptionChangeWriter->create($subscriptionChangeEntity);
|
||||
} catch (UniqueConstraintViolationException $ex) {
|
||||
$idEpisodeActionEntityToUpdate = $this->subscriptionChangeRepository->findByUrl($subscriptionChangeEntity->getUrl(), $userId)->getId();
|
||||
$subscriptionChangeEntity->setId($idEpisodeActionEntityToUpdate);
|
||||
$this->subscriptionChangeWriter->update($subscriptionChangeEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Core\SubscriptionChange;
|
||||
|
||||
class SubscriptionChangesReader {
|
||||
|
||||
/**
|
||||
* @param string $raw
|
||||
*
|
||||
* @return array|SubscriptionChange[]
|
||||
*/
|
||||
public function fromString(string $raw, bool $subscribed):? array {
|
||||
$urls = str_replace(["[", "]", " "], "", $raw);
|
||||
$urlList = explode(",", $urls);
|
||||
|
||||
if ($urlList[0] === "") {
|
||||
return [];
|
||||
}
|
||||
$subscriptionChanges = [];
|
||||
foreach ($urlList as $url) {
|
||||
$subscriptionChanges[] = new SubscriptionChange($url, $subscribed);
|
||||
}
|
||||
|
||||
return $subscriptionChanges;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Db\EpisodeAction;
|
||||
|
||||
use JsonSerializable;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
class EpisodeActionEntity extends Entity implements JsonSerializable {
|
||||
|
||||
protected $podcast;
|
||||
protected $episode;
|
||||
protected $action;
|
||||
protected $position;
|
||||
protected $started;
|
||||
protected $total;
|
||||
protected $timestamp;
|
||||
protected $userId;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('id','integer');
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
'action' => $this->action,
|
||||
'position' => $this->position,
|
||||
'started' => $this->started,
|
||||
'total' => $this->total,
|
||||
'timestamp' => (new \DateTime($this->timestamp))->format("Y-m-d\TH:i:s"),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Db\EpisodeAction;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper {
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'gpoddersync_episode_action', EpisodeActionEntity::class);
|
||||
}
|
||||
|
||||
public function findAll(\DateTime $sinceTimestamp, string $userId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where(
|
||||
$qb->expr()->gt('timestamp', $qb->createNamedParameter($sinceTimestamp, IQueryBuilder::PARAM_DATE))
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
|
||||
|
||||
);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function findByEpisode(string $episode, string $userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where(
|
||||
$qb->expr()->eq('episode', $qb->createNamedParameter($episode))
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
|
||||
);
|
||||
|
||||
try {
|
||||
return $this->findEntity($qb);
|
||||
} catch (DoesNotExistException $e) {
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Db\EpisodeAction;
|
||||
|
||||
class EpisodeActionRepository {
|
||||
/**
|
||||
* @var EpisodeActionMapper
|
||||
*/
|
||||
private EpisodeActionMapper $episodeActionMapper;
|
||||
|
||||
public function __construct(EpisodeActionMapper $episodeActionMapper) {
|
||||
$this->episodeActionMapper = $episodeActionMapper;
|
||||
}
|
||||
|
||||
public function findAll(\DateTime $sinceTimestamp, string $userId) : array {
|
||||
return $this->episodeActionMapper->findAll($sinceTimestamp, $userId);
|
||||
}
|
||||
|
||||
public function findByEpisode(string $episode, string $userId): EpisodeActionEntity {
|
||||
return $this->episodeActionMapper->findByEpisode($episode, $userId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Db\EpisodeAction;
|
||||
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
|
||||
class EpisodeActionWriter {
|
||||
|
||||
/**
|
||||
* @var EpisodeActionMapper
|
||||
*/
|
||||
private EpisodeActionMapper $episodeActionMapper;
|
||||
|
||||
public function __construct(EpisodeActionMapper $episodeActionMapper) {
|
||||
$this->episodeActionMapper = $episodeActionMapper;
|
||||
}
|
||||
|
||||
public function save(EpisodeActionEntity $episodeActionEntity): EpisodeActionEntity {
|
||||
return $this->episodeActionMapper->insert($episodeActionEntity);
|
||||
}
|
||||
|
||||
public function update(EpisodeActionEntity $episodeActionEntity) {
|
||||
return $this->episodeActionMapper->update($episodeActionEntity);
|
||||
|
||||
}
|
||||
|
||||
public function purge() {
|
||||
foreach ($this->episodeActionMapper->findAll() as $entity) {
|
||||
$this->episodeActionMapper->delete($entity);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Db\SubscriptionChange;
|
||||
|
||||
use JsonSerializable;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
class SubscriptionChangeEntity extends Entity implements JsonSerializable {
|
||||
|
||||
protected $url;
|
||||
protected $subscribed;
|
||||
protected $updated;
|
||||
protected $userId;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('id','integer');
|
||||
$this->addType('subscribed','boolean');
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'url' => $this->url,
|
||||
'subscribed' => $this->subscribed,
|
||||
'updated' => $this->updated,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Db\SubscriptionChange;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class SubscriptionChangeMapper extends \OCP\AppFramework\Db\QBMapper {
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'gpoddersync_subscriptions', SubscriptionChangeEntity::class);
|
||||
}
|
||||
|
||||
public function findAll(string $userId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where(
|
||||
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
|
||||
);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function findByUrl(string $url, string $userId): SubscriptionChangeEntity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where(
|
||||
$qb->expr()->eq('url', $qb->createNamedParameter($url))
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
|
||||
);
|
||||
|
||||
try {
|
||||
return $this->findEntity($qb);
|
||||
} catch (DoesNotExistException $e) {
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(SubscriptionChangeEntity $subscriptionChangeEntity) {
|
||||
$this->delete($subscriptionChangeEntity);
|
||||
}
|
||||
|
||||
public function findAllSubscriptionState(bool $subscribed, \DateTime $sinceTimestamp, string $userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('url')
|
||||
->from($this->getTableName())
|
||||
->where(
|
||||
$qb->expr()->eq('subscribed', $qb->createNamedParameter($subscribed, IQueryBuilder::PARAM_BOOL))
|
||||
)->andWhere(
|
||||
$qb->expr()->gt('updated', $qb->createNamedParameter($sinceTimestamp, IQueryBuilder::PARAM_DATE))
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
|
||||
);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Db\SubscriptionChange;
|
||||
|
||||
class SubscriptionChangeRepository {
|
||||
|
||||
/**
|
||||
* @var SubscriptionChangeMapper
|
||||
*/
|
||||
private SubscriptionChangeMapper $subscriptionChangeMapper;
|
||||
|
||||
public function __construct(SubscriptionChangeMapper $subscriptionChangeMapper) {
|
||||
$this->subscriptionChangeMapper = $subscriptionChangeMapper;
|
||||
}
|
||||
|
||||
public function findAll() : array {
|
||||
return $this->subscriptionChangeMapper->findAll();
|
||||
}
|
||||
|
||||
public function findByUrl(string $episode, string $userId): SubscriptionChangeEntity {
|
||||
return $this->subscriptionChangeMapper->findByUrl($episode, $userId);
|
||||
}
|
||||
|
||||
public function findAllSubscribed(\DateTime $sinceTimestamp, string $userId) {
|
||||
return $this->subscriptionChangeMapper->findAllSubscriptionState(true, $sinceTimestamp, $userId);
|
||||
}
|
||||
|
||||
public function findAllUnSubscribed(\DateTime $sinceTimestamp, string $userId) {
|
||||
return $this->subscriptionChangeMapper->findAllSubscriptionState(false, $sinceTimestamp, $userId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Db\SubscriptionChange;
|
||||
|
||||
|
||||
class SubscriptionChangeWriter {
|
||||
|
||||
/**
|
||||
* @var SubscriptionChangeMapper
|
||||
*/
|
||||
private SubscriptionChangeMapper $subscriptionChangeMapper;
|
||||
|
||||
public function __construct(SubscriptionChangeMapper $subscriptionChangeMapper) {
|
||||
$this->subscriptionChangeMapper = $subscriptionChangeMapper;
|
||||
}
|
||||
|
||||
|
||||
public function purge() {
|
||||
foreach ($this->subscriptionChangeMapper->findAll() as $entity) {
|
||||
$this->subscriptionChangeMapper->delete($entity);
|
||||
}
|
||||
}
|
||||
|
||||
public function create(SubscriptionChangeEntity $subscriptionChangeEntity): SubscriptionChangeEntity{
|
||||
return $this->subscriptionChangeMapper->insert($subscriptionChangeEntity);
|
||||
}
|
||||
|
||||
public function update(SubscriptionChangeEntity $subscriptionChangeEntity): SubscriptionChangeEntity{
|
||||
return $this->subscriptionChangeMapper->update($subscriptionChangeEntity);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Migration;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
|
||||
class Version0001Date20210520063113 extends \OCP\Migration\SimpleMigrationStep {
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
if (!$schema->hasTable('gpoddersync_episode_action')) {
|
||||
$table = $schema->createTable('gpoddersync_episode_action');
|
||||
$table->addColumn('id', 'integer', [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('podcast', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 500
|
||||
]);
|
||||
$table->addColumn('episode', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 500,
|
||||
'unique' => true,
|
||||
]);
|
||||
$table->addColumn('action', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 5
|
||||
]);
|
||||
$table->addColumn('position', 'integer', [
|
||||
'notnull' => true,
|
||||
]);
|
||||
|
||||
$table->addColumn('started', Types::INTEGER, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('total', Types::INTEGER, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('timestamp', Types::DATETIME_MUTABLE, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('user_id', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 200,
|
||||
]);
|
||||
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['episode', 'user_id'], 'gpoddersync_episode_user_id');
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Migration;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
|
||||
class Version0002Date20210524131313 extends \OCP\Migration\SimpleMigrationStep {
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
if (!$schema->hasTable('gpoddersync_subscriptions')) {
|
||||
$table = $schema->createTable('gpoddersync_subscriptions');
|
||||
$table->addColumn('id', 'integer', [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('url', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 500
|
||||
]);
|
||||
|
||||
$table->addColumn('subscribed', Types::BOOLEAN, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
|
||||
$table->addColumn('updated', Types::DATETIME_MUTABLE, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('user_id', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 200,
|
||||
]);
|
||||
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['url', "user_id"], 'subscriptions_url_user');
|
||||
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Migration;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
|
||||
class Version0003Date20210524131313 extends \OCP\Migration\SimpleMigrationStep {
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
$schema->dropTable('gpoddersync_subscriptions');
|
||||
$schema->dropTable('gpoddersync_episode_action');
|
||||
return $schema;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\GPodderSync\Tests\Integration\Controller;
|
||||
|
||||
use OCP\AppFramework\App;
|
||||
use Test\TestCase;
|
||||
|
||||
|
||||
/**
|
||||
* This test shows how to make a small Integration Test. Query your class
|
||||
* directly from the container, only pass in mocks if needed and run your tests
|
||||
* against the database
|
||||
*/
|
||||
class AppTest extends TestCase {
|
||||
|
||||
private $container;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$app = new App('gpoddersync');
|
||||
$this->container = $app->getContainer();
|
||||
}
|
||||
|
||||
public function testAppInstalled() {
|
||||
$appManager = $this->container->query('OCP\App\IAppManager');
|
||||
$this->assertTrue($appManager->isInstalled('gpoddersync'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Tests\Unit\Core\EpisodeAction;
|
||||
|
||||
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionReader;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
class EpisodeActionReaderTest extends PHPUnit_Framework_TestCase {
|
||||
public function testCreateFromString(): void {
|
||||
$reader = new EpisodeActionReader();
|
||||
$episodeAction = $reader->fromString('[EpisodeAction{podcast=\'https://feeds.simplecast.com/wEl4UUJZ\', episode=\'https://chrt.fm/track/47G541/injector.simplecastaudio.com/f16c3da7-cf46-4a42-99b7-8467255c6086/episodes/e8e24c01-6157-40e8-9b5a-45d539aeb7e6/audio/128/default.mp3?aid=rss_feed&awCollectionId=f16c3da7-cf46-4a42-99b7-8467255c6086&awEpisodeId=e8e24c01-6157-40e8-9b5a-45d539aeb7e6&feed=wEl4UUJZ\', action=PLAY, timestamp=Tue May 18 23:45:11 GMT+02:00 2021, started=31, position=36, total=2474}]');
|
||||
$this->assertSame("https://feeds.simplecast.com/wEl4UUJZ", $episodeAction->getPodcast());
|
||||
$this->assertSame("https://chrt.fm/track/47G541/injector.simplecastaudio.com/f16c3da7-cf46-4a42-99b7-8467255c6086/episodes/e8e24c01-6157-40e8-9b5a-45d539aeb7e6/audio/128/default.mp3?aid=rss_feed&awCollectionId=f16c3da7-cf46-4a42-99b7-8467255c6086&awEpisodeId=e8e24c01-6157-40e8-9b5a-45d539aeb7e6&feed=wEl4UUJZ", $episodeAction->getEpisode());
|
||||
$this->assertSame("PLAY", $episodeAction->getAction());
|
||||
$this->assertSame("Tue May 18 23:45:11 GMT+02:00 2021", $episodeAction->getTimestamp());
|
||||
$this->assertSame(31, $episodeAction->getStarted());
|
||||
$this->assertSame(36, $episodeAction->getPosition());
|
||||
$this->assertSame(2474, $episodeAction->getTotal());
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Tests\Unit\Core\SubscriptionChange;
|
||||
|
||||
use OCA\GPodderSync\Core\SubscriptionChange\SubscriptionChangesReader;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
class SubscriptionChangeReaderTest extends PHPUnit_Framework_TestCase {
|
||||
public function testCreateFromString(): void {
|
||||
$reader = new SubscriptionChangesReader();
|
||||
$subscriptionChange = $reader->fromString('[https://feeds.megaphone.fm/HSW8286374095]', true);
|
||||
$this->assertCount(1, $subscriptionChange);
|
||||
$this->assertSame("https://feeds.megaphone.fm/HSW8286374095", $subscriptionChange[0]->getUrl());
|
||||
}
|
||||
|
||||
public function testCreateFromEmptyString(): void {
|
||||
$reader = new SubscriptionChangesReader();
|
||||
$subscriptionChange = $reader->fromString('[]', true);
|
||||
$this->assertCount(0, $subscriptionChange);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\GPodderSync\Tests\Unit\Core\SubscriptionChange;
|
||||
|
||||
use OCA\GPodderSync\Core\SubscriptionChange\SubscriptionChangeRequestParser;
|
||||
use OCA\GPodderSync\Core\SubscriptionChange\SubscriptionChangesReader;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
class SubscriptionChangeRequestParserTest extends PHPUnit_Framework_TestCase {
|
||||
public function testSubscriptionRequestConvertsToSubscriptionChangeList() {
|
||||
$subscriptionChangesParser = new SubscriptionChangeRequestParser(
|
||||
new SubscriptionChangesReader(),
|
||||
);
|
||||
|
||||
$subscriptionChanges = $subscriptionChangesParser->createSubscriptionChangeList('[https://feeds.simplecast.com/54nAGcIl]','[]');
|
||||
$this->assertCount(1, $subscriptionChanges);
|
||||
$this->assertSame('https://feeds.simplecast.com/54nAGcIl', $subscriptionChanges[0]->getUrl());
|
||||
$this->assertTrue($subscriptionChanges[0]->isSubscribed());
|
||||
}
|
||||
|
||||
public function testSubscriptionRequestWithMultipleChangesConvertsToSubscriptionChangeList() {
|
||||
$subscriptionChangesParser = new SubscriptionChangeRequestParser(
|
||||
new SubscriptionChangesReader(),
|
||||
);
|
||||
|
||||
$subscriptionChanges = $subscriptionChangesParser->createSubscriptionChangeList(
|
||||
'[https://podcastfeeds.nbcnews.com/dateline-nbc,https://feeds.megaphone.fm/ADL9840290619]',
|
||||
'[]');
|
||||
$this->assertCount(2, $subscriptionChanges);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
if (!defined('PHPUNIT_RUN')) {
|
||||
define('PHPUNIT_RUN', 1);
|
||||
}
|
||||
|
||||
require_once __DIR__.'/../../../lib/base.php';
|
||||
|
||||
// Fix for "Autoload path not allowed: .../tests/lib/testcase.php"
|
||||
\OC::$loader->addValidRoot(OC::$SERVERROOT . '/tests');
|
||||
|
||||
// Fix for "Autoload path not allowed: .../gpoddersync/tests/testcase.php"
|
||||
\OC_App::loadApp('gpoddersync');
|
||||
|
||||
if(!class_exists('PHPUnit_Framework_TestCase')) {
|
||||
require_once('PHPUnit/Autoload.php');
|
||||
}
|
||||
|
||||
OC_Hook::clear();
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit101c320a2bec76e94ce31673caacea82::getLoader();
|
|
@ -0,0 +1 @@
|
|||
../phpunit/phpunit/phpunit
|
Loading…
Reference in New Issue