provide episode_action timestamps as UTC in api response

This commit is contained in:
thrillfall 2021-10-05 20:45:06 +02:00
parent aa024e55f8
commit ac1acf079b
9 changed files with 142 additions and 25 deletions

View File

@ -12,6 +12,7 @@ class EpisodeAction {
private int $position;
private int $total;
private ?string $guid;
private ?int $id;
public function __construct(
string $podcast,
@ -21,7 +22,8 @@ class EpisodeAction {
int $started,
int $position,
int $total,
?string $guid
?string $guid,
?int $id
) {
$this->podcast = $podcast;
$this->episode = $episode;
@ -31,6 +33,7 @@ class EpisodeAction {
$this->position = $position;
$this->total = $total;
$this->guid = $guid;
$this->id = $id;
}
/**
@ -88,5 +91,13 @@ class EpisodeAction {
return $this->guid;
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
}

View File

@ -44,6 +44,7 @@ class EpisodeActionReader
(int)$matches["position"],
(int)$matches["total"],
$matches["guid"] ?? null,
null,
);
break;
}

View File

@ -55,11 +55,11 @@ class EpisodeActionSaver
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)
->setTimezone(new DateTimeZone('UTC'))
->format("Y-m-d\TH:i:s");
->format("U");
}
private function updateEpisodeAction(
@ -68,23 +68,23 @@ class EpisodeActionSaver
): EpisodeActionEntity
{
$identifier = $episodeActionEntity->getGuid() ?? $episodeActionEntity->getEpisode();
$episodeActionEntityToUpdate = $this->episodeActionRepository->findByEpisodeIdentifier(
$episodeActionToUpdate = $this->episodeActionRepository->findByEpisodeIdentifier(
$identifier,
$userId
);
if ($episodeActionEntityToUpdate === null && $episodeActionEntity->getGuid() !== null) {
$episodeActionEntityToUpdate = $this->getOldEpisodeActionByEpisodeUrl($episodeActionEntity->getEpisode(), $userId);
if ($episodeActionToUpdate === null && $episodeActionEntity->getGuid() !== null) {
$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);
}
private function getOldEpisodeActionByEpisodeUrl(string $episodeUrl, string $userId): ?EpisodeActionEntity
private function getOldEpisodeActionByEpisodeUrl(string $episodeUrl, string $userId): ?EpisodeAction
{
return $this->episodeActionRepository->findByEpisodeIdentifier(
$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) {
$episodeActionEntity->setGuid($existingGuid);
}
@ -110,7 +110,7 @@ class EpisodeActionSaver
$episodeActionEntity->setPosition($episodeAction->getPosition());
$episodeActionEntity->setStarted($episodeAction->getStarted());
$episodeActionEntity->setTotal($episodeAction->getTotal());
$episodeActionEntity->setTimestamp($this->convertTimestampTo($episodeAction->getTimestamp()));
$episodeActionEntity->setTimestampEpoch($this->convertTimestampToUnixEpoch($episodeAction->getTimestamp()));
$episodeActionEntity->setUserId($userId);
return $episodeActionEntity;

View File

@ -33,8 +33,12 @@ class EpisodeActionEntity extends Entity implements JsonSerializable {
'position' => $this->position,
'started' => $this->started,
'total' => $this->total,
'timestamp' => (new \DateTime($this->timestamp))->format("Y-m-d\TH:i:s"),
'timestamp_epoch' => $this->timestampEpoch,
'timestamp' => $this->timestampEpoch,
];
}
public function getTimestampEpoch() : int
{
return (int) $this->timestampEpoch;
}
}

View File

@ -3,10 +3,12 @@ declare(strict_types=1);
namespace OCA\GPodderSync\Db\EpisodeAction;
use OCA\GPodderSync\Core\EpisodeAction\EpisodeAction;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use Safe\DateTime;
class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper
{
@ -30,6 +32,7 @@ class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper
);
return $this->findEntities($qb);
}
public function findByEpisodeIdentifier(string $episodeIdentifier, string $userId) : ?EpisodeActionEntity
@ -48,7 +51,7 @@ class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper
);
try {
/** @var EpisodeActionEntity $episodeActionEntity*/
/** @var EpisodeActionEntity $episodeActionEntity */
$episodeActionEntity = $this->findEntity($qb);
return $episodeActionEntity;
@ -58,4 +61,6 @@ class EpisodeActionMapper extends \OCP\AppFramework\Db\QBMapper
return null;
}
}

View File

@ -3,6 +3,8 @@ declare(strict_types=1);
namespace OCA\GPodderSync\Db\EpisodeAction;
use OCA\GPodderSync\Core\EpisodeAction\EpisodeAction;
class EpisodeActionRepository {
/**
* @var EpisodeActionMapper
@ -14,11 +16,47 @@ class EpisodeActionRepository {
}
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 {
return $this->episodeActionMapper->findByEpisodeIdentifier($identifier, $userId);
public function findByEpisodeIdentifier(string $identifier, string $userId): ?EpisodeAction {
$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(),
);
}
}

View File

@ -15,6 +15,7 @@ class Version0005Date20211004110900 extends SimpleMigrationStep {
$schema = $schemaClosure();
$table = $schema->getTable('gpodder_episode_action');
$table->changeColumn('timestamp', ['notnull' => false]);
$table->addColumn('timestamp_epoch', Types::INTEGER, [
'notnull' => false,
'default' => 0,

View 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());
}
}

View File

@ -7,6 +7,7 @@ use OC\AllConfig;
use OC\Log;
use OC\Migration\SimpleOutput;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionMapper;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter;
use OCA\GPodderSync\Migration\TimestampMigration;
@ -28,7 +29,7 @@ class TimestampMigrationTest extends TestCase
const TEST_GUID_1234 = "test_uuid_1234";
const ADMIN = "admin";
private EpisodeActionWriter $episodeActionWriter;
private EpisodeActionRepository $episodeActionRepository;
private EpisodeActionMapper $episodeActionMapper;
private IDBConnection $dbConnection;
private IConfig $migrationConfig;
@ -38,7 +39,7 @@ class TimestampMigrationTest extends TestCase
$app = new App('gpoddersync');
$this->container = $app->getContainer();
$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->migrationConfig = $this->container->get(AllConfig::class );
}
@ -66,19 +67,19 @@ class TimestampMigrationTest extends TestCase
$episodeActionEntity->setGuid($guid);
$this->episodeActionWriter->save($episodeActionEntity);
$episodeActionEntityBeforeConversion = $this->episodeActionRepository->findByEpisodeIdentifier($guid, self::ADMIN);
$episodeActionBeforeConversion = $this->episodeActionMapper->findByEpisodeIdentifier($guid, self::ADMIN);
$this->assertEquals(
0,
$episodeActionEntityBeforeConversion->getTimestampEpoch()
$episodeActionBeforeConversion->getTimestampEpoch()
);
$timestampMigration = new TimestampMigration($this->dbConnection, $this->migrationConfig);
$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(
(int)(new \DateTime($episodeActionEntity->getTimestamp()))->format("U"),
$episodeActionEntityAfterConversion->getTimestampEpoch()
1629676736,
$episodeActionAfterConversion->getTimestampEpoch()
);
}