provide episode_action timestamps as UTC in api response
This commit is contained in:
parent
aa024e55f8
commit
ac1acf079b
@ -12,6 +12,7 @@ class EpisodeAction {
|
|||||||
private int $position;
|
private int $position;
|
||||||
private int $total;
|
private int $total;
|
||||||
private ?string $guid;
|
private ?string $guid;
|
||||||
|
private ?int $id;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $podcast,
|
string $podcast,
|
||||||
@ -21,7 +22,8 @@ class EpisodeAction {
|
|||||||
int $started,
|
int $started,
|
||||||
int $position,
|
int $position,
|
||||||
int $total,
|
int $total,
|
||||||
?string $guid
|
?string $guid,
|
||||||
|
?int $id
|
||||||
) {
|
) {
|
||||||
$this->podcast = $podcast;
|
$this->podcast = $podcast;
|
||||||
$this->episode = $episode;
|
$this->episode = $episode;
|
||||||
@ -31,6 +33,7 @@ class EpisodeAction {
|
|||||||
$this->position = $position;
|
$this->position = $position;
|
||||||
$this->total = $total;
|
$this->total = $total;
|
||||||
$this->guid = $guid;
|
$this->guid = $guid;
|
||||||
|
$this->id = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,5 +91,13 @@ class EpisodeAction {
|
|||||||
return $this->guid;
|
return $this->guid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ class EpisodeActionReader
|
|||||||
(int)$matches["position"],
|
(int)$matches["position"],
|
||||||
(int)$matches["total"],
|
(int)$matches["total"],
|
||||||
$matches["guid"] ?? null,
|
$matches["guid"] ?? null,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -55,11 +55,11 @@ class EpisodeActionSaver
|
|||||||
return $episodeActionEntities;
|
return $episodeActionEntities;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function convertTimestampTo(string $timestamp): string
|
private function convertTimestampToUnixEpoch(string $timestamp): string
|
||||||
{
|
{
|
||||||
return \DateTime::createFromFormat('D F d H:i:s T Y', $timestamp)
|
return \DateTime::createFromFormat('D F d H:i:s T Y', $timestamp)
|
||||||
->setTimezone(new DateTimeZone('UTC'))
|
->setTimezone(new DateTimeZone('UTC'))
|
||||||
->format("Y-m-d\TH:i:s");
|
->format("U");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateEpisodeAction(
|
private function updateEpisodeAction(
|
||||||
@ -68,23 +68,23 @@ class EpisodeActionSaver
|
|||||||
): EpisodeActionEntity
|
): EpisodeActionEntity
|
||||||
{
|
{
|
||||||
$identifier = $episodeActionEntity->getGuid() ?? $episodeActionEntity->getEpisode();
|
$identifier = $episodeActionEntity->getGuid() ?? $episodeActionEntity->getEpisode();
|
||||||
$episodeActionEntityToUpdate = $this->episodeActionRepository->findByEpisodeIdentifier(
|
$episodeActionToUpdate = $this->episodeActionRepository->findByEpisodeIdentifier(
|
||||||
$identifier,
|
$identifier,
|
||||||
$userId
|
$userId
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($episodeActionEntityToUpdate === null && $episodeActionEntity->getGuid() !== null) {
|
if ($episodeActionToUpdate === null && $episodeActionEntity->getGuid() !== null) {
|
||||||
$episodeActionEntityToUpdate = $this->getOldEpisodeActionByEpisodeUrl($episodeActionEntity->getEpisode(), $userId);
|
$episodeActionToUpdate = $this->getOldEpisodeActionByEpisodeUrl($episodeActionEntity->getEpisode(), $userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
$episodeActionEntity->setId($episodeActionEntityToUpdate->getId());
|
$episodeActionEntity->setId($episodeActionToUpdate->getId());
|
||||||
|
|
||||||
$this->ensureGuidDoesNotGetNulledWithOldData($episodeActionEntityToUpdate, $episodeActionEntity);
|
$this->ensureGuidDoesNotGetNulledWithOldData($episodeActionToUpdate, $episodeActionEntity);
|
||||||
|
|
||||||
return $this->episodeActionWriter->update($episodeActionEntity);
|
return $this->episodeActionWriter->update($episodeActionEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getOldEpisodeActionByEpisodeUrl(string $episodeUrl, string $userId): ?EpisodeActionEntity
|
private function getOldEpisodeActionByEpisodeUrl(string $episodeUrl, string $userId): ?EpisodeAction
|
||||||
{
|
{
|
||||||
return $this->episodeActionRepository->findByEpisodeIdentifier(
|
return $this->episodeActionRepository->findByEpisodeIdentifier(
|
||||||
$episodeUrl,
|
$episodeUrl,
|
||||||
@ -92,9 +92,9 @@ class EpisodeActionSaver
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ensureGuidDoesNotGetNulledWithOldData(EpisodeActionEntity $episodeActionEntityToUpdate, EpisodeActionEntity $episodeActionEntity): void
|
private function ensureGuidDoesNotGetNulledWithOldData(EpisodeAction $episodeActionToUpdate, EpisodeActionEntity $episodeActionEntity): void
|
||||||
{
|
{
|
||||||
$existingGuid = $episodeActionEntityToUpdate->getGuid();
|
$existingGuid = $episodeActionToUpdate->getGuid();
|
||||||
if ($existingGuid !== null && $episodeActionEntity->getGuid() == null) {
|
if ($existingGuid !== null && $episodeActionEntity->getGuid() == null) {
|
||||||
$episodeActionEntity->setGuid($existingGuid);
|
$episodeActionEntity->setGuid($existingGuid);
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ class EpisodeActionSaver
|
|||||||
$episodeActionEntity->setPosition($episodeAction->getPosition());
|
$episodeActionEntity->setPosition($episodeAction->getPosition());
|
||||||
$episodeActionEntity->setStarted($episodeAction->getStarted());
|
$episodeActionEntity->setStarted($episodeAction->getStarted());
|
||||||
$episodeActionEntity->setTotal($episodeAction->getTotal());
|
$episodeActionEntity->setTotal($episodeAction->getTotal());
|
||||||
$episodeActionEntity->setTimestamp($this->convertTimestampTo($episodeAction->getTimestamp()));
|
$episodeActionEntity->setTimestampEpoch($this->convertTimestampToUnixEpoch($episodeAction->getTimestamp()));
|
||||||
$episodeActionEntity->setUserId($userId);
|
$episodeActionEntity->setUserId($userId);
|
||||||
|
|
||||||
return $episodeActionEntity;
|
return $episodeActionEntity;
|
||||||
|
@ -33,8 +33,12 @@ class EpisodeActionEntity extends Entity implements JsonSerializable {
|
|||||||
'position' => $this->position,
|
'position' => $this->position,
|
||||||
'started' => $this->started,
|
'started' => $this->started,
|
||||||
'total' => $this->total,
|
'total' => $this->total,
|
||||||
'timestamp' => (new \DateTime($this->timestamp))->format("Y-m-d\TH:i:s"),
|
'timestamp' => $this->timestampEpoch,
|
||||||
'timestamp_epoch' => $this->timestampEpoch,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTimestampEpoch() : int
|
||||||
|
{
|
||||||
|
return (int) $this->timestampEpoch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\GPodderSync\Db\EpisodeAction;
|
namespace OCA\GPodderSync\Db\EpisodeAction;
|
||||||
|
|
||||||
|
use OCA\GPodderSync\Core\EpisodeAction\EpisodeAction;
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
use Safe\DateTime;
|
||||||
|
|
||||||
class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper
|
class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper
|
||||||
{
|
{
|
||||||
@ -30,6 +32,7 @@ class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper
|
|||||||
);
|
);
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findByEpisodeIdentifier(string $episodeIdentifier, string $userId) : ?EpisodeActionEntity
|
public function findByEpisodeIdentifier(string $episodeIdentifier, string $userId) : ?EpisodeActionEntity
|
||||||
@ -58,4 +61,6 @@ class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\GPodderSync\Db\EpisodeAction;
|
namespace OCA\GPodderSync\Db\EpisodeAction;
|
||||||
|
|
||||||
|
use OCA\GPodderSync\Core\EpisodeAction\EpisodeAction;
|
||||||
|
|
||||||
class EpisodeActionRepository {
|
class EpisodeActionRepository {
|
||||||
/**
|
/**
|
||||||
* @var EpisodeActionMapper
|
* @var EpisodeActionMapper
|
||||||
@ -14,11 +16,47 @@ class EpisodeActionRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function findAll(\DateTime $sinceTimestamp, string $userId) : array {
|
public function findAll(\DateTime $sinceTimestamp, string $userId) : array {
|
||||||
return $this->episodeActionMapper->findAll($sinceTimestamp, $userId);
|
$episodeActions = [];
|
||||||
|
foreach ($this->episodeActionMapper->findAll($sinceTimestamp, $userId) as $entity) {
|
||||||
|
$episodeActions[] = $this->mapEntityToEpisodeAction($entity);
|
||||||
|
}
|
||||||
|
return $episodeActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findByEpisodeIdentifier(string $identifier, string $userId): ?EpisodeActionEntity {
|
public function findByEpisodeIdentifier(string $identifier, string $userId): ?EpisodeAction {
|
||||||
return $this->episodeActionMapper->findByEpisodeIdentifier($identifier, $userId);
|
$episodeActionEntity = $this->episodeActionMapper->findByEpisodeIdentifier($identifier, $userId);
|
||||||
|
|
||||||
|
if ($episodeActionEntity === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->mapEntityToEpisodeAction(
|
||||||
|
$episodeActionEntity
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param EpisodeActionEntity $episodeActionEntity
|
||||||
|
* @return EpisodeAction
|
||||||
|
* @throws \Safe\Exceptions\DatetimeException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function mapEntityToEpisodeAction(EpisodeActionEntity $episodeActionEntity): EpisodeAction
|
||||||
|
{
|
||||||
|
return new EpisodeAction(
|
||||||
|
$episodeActionEntity->getPodcast(),
|
||||||
|
$episodeActionEntity->getEpisode(),
|
||||||
|
$episodeActionEntity->getAction(),
|
||||||
|
\DateTime::createFromFormat(
|
||||||
|
"U",
|
||||||
|
(string)$episodeActionEntity->getTimestampEpoch())
|
||||||
|
->format("Y-m-d\TH:i:s"),
|
||||||
|
$episodeActionEntity->getStarted(),
|
||||||
|
$episodeActionEntity->getPosition(),
|
||||||
|
$episodeActionEntity->getTotal(),
|
||||||
|
$episodeActionEntity->getGuid(),
|
||||||
|
$episodeActionEntity->getId(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ class Version0005Date20211004110900 extends SimpleMigrationStep {
|
|||||||
$schema = $schemaClosure();
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
$table = $schema->getTable('gpodder_episode_action');
|
$table = $schema->getTable('gpodder_episode_action');
|
||||||
|
$table->changeColumn('timestamp', ['notnull' => false]);
|
||||||
$table->addColumn('timestamp_epoch', Types::INTEGER, [
|
$table->addColumn('timestamp_epoch', Types::INTEGER, [
|
||||||
'notnull' => false,
|
'notnull' => false,
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
|
56
tests/Integration/EpisodeActionRepositoryTest.php
Normal file
56
tests/Integration/EpisodeActionRepositoryTest.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace tests\Integration;
|
||||||
|
|
||||||
|
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionSaver;
|
||||||
|
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
|
||||||
|
use OCP\AppFramework\App;
|
||||||
|
use OCP\AppFramework\IAppContainer;
|
||||||
|
|
||||||
|
class EpisodeActionRepositoryTest extends \Test\TestCase
|
||||||
|
{
|
||||||
|
private const USER_ID_0 = "testuser0";
|
||||||
|
|
||||||
|
private IAppContainer $container;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
$app = new App('gpoddersync');
|
||||||
|
$this->container = $app->getContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTimestampOutputIsUTCHumandReadable() : void
|
||||||
|
{
|
||||||
|
/** @var EpisodeActionSaver $episodeActionSaver */
|
||||||
|
$episodeActionSaver = $this->container->get(EpisodeActionSaver::class);
|
||||||
|
|
||||||
|
$episodeUrl = uniqid("test_https://dts.podtrac.com/");
|
||||||
|
|
||||||
|
$timestampHumanReadable = "2021-08-22T23:58:56";
|
||||||
|
$guid = uniqid("test_gid://art19-episode-locator/V0/Ktd");
|
||||||
|
|
||||||
|
$savedEpisodeActionEntity = $episodeActionSaver->saveEpisodeActions(
|
||||||
|
"[EpisodeAction{podcast='https://rss.art19.com/dr-death-s3-miracle-man', episode='{$episodeUrl}', guid='{$guid}', action=PLAY, timestamp=Mon Aug 23 01:58:56 GMT+02:00 2021, started=47, position=54, total=2252}]",
|
||||||
|
self::USER_ID_0
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
self::assertSame(1629676736, $savedEpisodeActionEntity->getTimestampEpoch());
|
||||||
|
|
||||||
|
$timestampOutputFormatted =
|
||||||
|
(\DateTime::createFromFormat("U", (string)$savedEpisodeActionEntity->getTimestampEpoch()))
|
||||||
|
->setTimezone(new \DateTimeZone('UTC'))
|
||||||
|
->format('Y-m-d\TH:i:s');
|
||||||
|
self::assertSame(
|
||||||
|
$timestampHumanReadable,
|
||||||
|
$timestampOutputFormatted
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var $episodeActionRepository EpisodeActionRepository */
|
||||||
|
$episodeActionRepository = $this->container->get(EpisodeActionRepository::class);
|
||||||
|
|
||||||
|
$retrievedEpisodeActionEntity = $episodeActionRepository->findByEpisodeIdentifier($guid, self::USER_ID_0);
|
||||||
|
self::assertSame('2021-08-22T23:58:56', $retrievedEpisodeActionEntity->getTimestamp());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ use OC\AllConfig;
|
|||||||
use OC\Log;
|
use OC\Log;
|
||||||
use OC\Migration\SimpleOutput;
|
use OC\Migration\SimpleOutput;
|
||||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity;
|
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity;
|
||||||
|
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionMapper;
|
||||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
|
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
|
||||||
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter;
|
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter;
|
||||||
use OCA\GPodderSync\Migration\TimestampMigration;
|
use OCA\GPodderSync\Migration\TimestampMigration;
|
||||||
@ -28,7 +29,7 @@ class TimestampMigrationTest extends TestCase
|
|||||||
const TEST_GUID_1234 = "test_uuid_1234";
|
const TEST_GUID_1234 = "test_uuid_1234";
|
||||||
const ADMIN = "admin";
|
const ADMIN = "admin";
|
||||||
private EpisodeActionWriter $episodeActionWriter;
|
private EpisodeActionWriter $episodeActionWriter;
|
||||||
private EpisodeActionRepository $episodeActionRepository;
|
private EpisodeActionMapper $episodeActionMapper;
|
||||||
private IDBConnection $dbConnection;
|
private IDBConnection $dbConnection;
|
||||||
private IConfig $migrationConfig;
|
private IConfig $migrationConfig;
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ class TimestampMigrationTest extends TestCase
|
|||||||
$app = new App('gpoddersync');
|
$app = new App('gpoddersync');
|
||||||
$this->container = $app->getContainer();
|
$this->container = $app->getContainer();
|
||||||
$this->episodeActionWriter = $this->container->get(EpisodeActionWriter::class);
|
$this->episodeActionWriter = $this->container->get(EpisodeActionWriter::class);
|
||||||
$this->episodeActionRepository = $this->container->get(EpisodeActionRepository::class);
|
$this->episodeActionMapper = $this->container->get(EpisodeActionMapper::class);
|
||||||
$this->dbConnection = $this->container->get(IDBConnection::class);
|
$this->dbConnection = $this->container->get(IDBConnection::class);
|
||||||
$this->migrationConfig = $this->container->get(AllConfig::class );
|
$this->migrationConfig = $this->container->get(AllConfig::class );
|
||||||
}
|
}
|
||||||
@ -66,19 +67,19 @@ class TimestampMigrationTest extends TestCase
|
|||||||
$episodeActionEntity->setGuid($guid);
|
$episodeActionEntity->setGuid($guid);
|
||||||
$this->episodeActionWriter->save($episodeActionEntity);
|
$this->episodeActionWriter->save($episodeActionEntity);
|
||||||
|
|
||||||
$episodeActionEntityBeforeConversion = $this->episodeActionRepository->findByEpisodeIdentifier($guid, self::ADMIN);
|
$episodeActionBeforeConversion = $this->episodeActionMapper->findByEpisodeIdentifier($guid, self::ADMIN);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
0,
|
0,
|
||||||
$episodeActionEntityBeforeConversion->getTimestampEpoch()
|
$episodeActionBeforeConversion->getTimestampEpoch()
|
||||||
);
|
);
|
||||||
|
|
||||||
$timestampMigration = new TimestampMigration($this->dbConnection, $this->migrationConfig);
|
$timestampMigration = new TimestampMigration($this->dbConnection, $this->migrationConfig);
|
||||||
$timestampMigration->run(new SimpleOutput(new Log(new TestWriter()), "gpoddersync"));
|
$timestampMigration->run(new SimpleOutput(new Log(new TestWriter()), "gpoddersync"));
|
||||||
|
|
||||||
$episodeActionEntityAfterConversion = $this->episodeActionRepository->findByEpisodeIdentifier($guid, self::ADMIN);
|
$episodeActionAfterConversion = $this->episodeActionMapper->findByEpisodeIdentifier($guid, self::ADMIN);
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
(int)(new \DateTime($episodeActionEntity->getTimestamp()))->format("U"),
|
1629676736,
|
||||||
$episodeActionEntityAfterConversion->getTimestampEpoch()
|
$episodeActionAfterConversion->getTimestampEpoch()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user