fix #109 search for episode action by guid only (retry with episode url if nothing found) thus avoid multiple results

This commit is contained in:
thrillfall 2023-02-24 16:48:16 +01:00
parent 4999b96456
commit a1893061ce
8 changed files with 141 additions and 116 deletions

View File

@ -13,107 +13,106 @@ use OCP\DB\Exception;
class EpisodeActionSaver class EpisodeActionSaver
{ {
private EpisodeActionRepository $episodeActionRepository; private EpisodeActionRepository $episodeActionRepository;
private EpisodeActionWriter $episodeActionWriter; private EpisodeActionWriter $episodeActionWriter;
private EpisodeActionReader $episodeActionReader; private EpisodeActionReader $episodeActionReader;
private const DATETIME_FORMAT = 'Y-m-d\TH:i:s'; private const DATETIME_FORMAT = 'Y-m-d\TH:i:s';
public function __construct( public function __construct(
EpisodeActionRepository $episodeActionRepository, EpisodeActionRepository $episodeActionRepository,
EpisodeActionWriter $episodeActionWriter, EpisodeActionWriter $episodeActionWriter,
EpisodeActionReader $episodeActionReader EpisodeActionReader $episodeActionReader
) )
{ {
$this->episodeActionRepository = $episodeActionRepository; $this->episodeActionRepository = $episodeActionRepository;
$this->episodeActionWriter = $episodeActionWriter; $this->episodeActionWriter = $episodeActionWriter;
$this->episodeActionReader = $episodeActionReader; $this->episodeActionReader = $episodeActionReader;
} }
/** public function saveEpisodeActions(array $episodeActionsArray, string $userId): array
* @param array $episodeActionsArray {
* @param string $userId $episodeActions = $this->episodeActionReader->fromArray($episodeActionsArray);
* @return EpisodeActionEntity[]
*/
public function saveEpisodeActions(array $episodeActionsArray, string $userId): array
{
$episodeActions = $this->episodeActionReader->fromArray($episodeActionsArray);
$episodeActionEntities = []; $episodeActionEntities = [];
foreach ($episodeActions as $episodeAction) { foreach ($episodeActions as $episodeAction) {
$episodeActionEntity = $this->hydrateEpisodeActionEntity($episodeAction, $userId); $episodeActionEntity = $this->hydrateEpisodeActionEntity($episodeAction, $userId);
try { try {
$episodeActionEntities[] = $this->episodeActionWriter->save($episodeActionEntity); $episodeActionEntities[] = $this->episodeActionWriter->save($episodeActionEntity);
} catch (UniqueConstraintViolationException $uniqueConstraintViolationException) { } catch (UniqueConstraintViolationException $uniqueConstraintViolationException) {
$episodeActionEntities[] = $this->updateEpisodeAction($episodeActionEntity, $userId); $episodeActionEntities[] = $this->updateEpisodeAction($episodeActionEntity, $userId);
} catch (Exception $exception) { } catch (Exception $exception) {
if ($exception->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { if ($exception->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
$episodeActionEntities[] = $this->updateEpisodeAction($episodeActionEntity, $userId); $episodeActionEntities[] = $this->updateEpisodeAction($episodeActionEntity, $userId);
} }
} }
} }
return $episodeActionEntities; return $episodeActionEntities;
} }
private function convertTimestampToUnixEpoch(string $timestamp): string private function convertTimestampToUnixEpoch(string $timestamp): string
{ {
return DateTime::createFromFormat(self::DATETIME_FORMAT, $timestamp) return DateTime::createFromFormat(self::DATETIME_FORMAT, $timestamp)
->format("U"); ->format("U");
} }
private function updateEpisodeAction( private function updateEpisodeAction(
EpisodeActionEntity $episodeActionEntity, EpisodeActionEntity $episodeActionEntity,
string $userId string $userId
): EpisodeActionEntity ): EpisodeActionEntity
{ {
$identifier = $episodeActionEntity->getGuid() ?? $episodeActionEntity->getEpisode(); $episodeActionToUpdate = $this->findEpisodeActionToUpdate($episodeActionEntity, $userId);
$episodeActionToUpdate = $this->episodeActionRepository->findByEpisodeIdentifier(
$identifier,
$userId
);
if ($episodeActionToUpdate === null && $episodeActionEntity->getGuid() !== null) { $episodeActionEntity->setId($episodeActionToUpdate->getId());
$episodeActionToUpdate = $this->getOldEpisodeActionByEpisodeUrl($episodeActionEntity->getEpisode(), $userId);
}
$episodeActionEntity->setId($episodeActionToUpdate->getId()); $this->ensureGuidDoesNotGetNulledWithOldData($episodeActionToUpdate, $episodeActionEntity);
$this->ensureGuidDoesNotGetNulledWithOldData($episodeActionToUpdate, $episodeActionEntity); return $this->episodeActionWriter->update($episodeActionEntity);
}
return $this->episodeActionWriter->update($episodeActionEntity); private function ensureGuidDoesNotGetNulledWithOldData(EpisodeAction $episodeActionToUpdate, EpisodeActionEntity $episodeActionEntity): void
} {
$existingGuid = $episodeActionToUpdate->getGuid();
if ($existingGuid !== null && $episodeActionEntity->getGuid() === null) {
$episodeActionEntity->setGuid($existingGuid);
}
}
private function getOldEpisodeActionByEpisodeUrl(string $episodeUrl, string $userId): ?EpisodeAction private function hydrateEpisodeActionEntity(EpisodeAction $episodeAction, string $userId): EpisodeActionEntity
{ {
return $this->episodeActionRepository->findByEpisodeIdentifier( $episodeActionEntity = new EpisodeActionEntity();
$episodeUrl, $episodeActionEntity->setPodcast($episodeAction->getPodcast());
$userId $episodeActionEntity->setEpisode($episodeAction->getEpisode());
); $episodeActionEntity->setGuid($episodeAction->getGuid());
} $episodeActionEntity->setAction($episodeAction->getAction());
$episodeActionEntity->setPosition($episodeAction->getPosition());
$episodeActionEntity->setStarted($episodeAction->getStarted());
$episodeActionEntity->setTotal($episodeAction->getTotal());
$episodeActionEntity->setTimestampEpoch($this->convertTimestampToUnixEpoch($episodeAction->getTimestamp()));
$episodeActionEntity->setUserId($userId);
private function ensureGuidDoesNotGetNulledWithOldData(EpisodeAction $episodeActionToUpdate, EpisodeActionEntity $episodeActionEntity): void return $episodeActionEntity;
{ }
$existingGuid = $episodeActionToUpdate->getGuid();
if ($existingGuid !== null && $episodeActionEntity->getGuid() === null) {
$episodeActionEntity->setGuid($existingGuid);
}
}
private function hydrateEpisodeActionEntity(EpisodeAction $episodeAction, string $userId): EpisodeActionEntity private function findEpisodeActionToUpdate(EpisodeActionEntity $episodeActionEntity, string $userId): ?EpisodeAction
{ {
$episodeActionEntity = new EpisodeActionEntity(); $episodeAction = null;
$episodeActionEntity->setPodcast($episodeAction->getPodcast()); if ($episodeActionEntity->getGuid() !== null) {
$episodeActionEntity->setEpisode($episodeAction->getEpisode()); $episodeAction = $this->episodeActionRepository->findByGuid(
$episodeActionEntity->setGuid($episodeAction->getGuid()); $episodeActionEntity->getGuid(),
$episodeActionEntity->setAction($episodeAction->getAction()); $userId
$episodeActionEntity->setPosition($episodeAction->getPosition()); );
$episodeActionEntity->setStarted($episodeAction->getStarted()); }
$episodeActionEntity->setTotal($episodeAction->getTotal());
$episodeActionEntity->setTimestampEpoch($this->convertTimestampToUnixEpoch($episodeAction->getTimestamp()));
$episodeActionEntity->setUserId($userId);
return $episodeActionEntity; if ($episodeAction === null) {
} $episodeAction = $this->episodeActionRepository->findByEpisodeUrl(
$episodeActionEntity->getEpisode(),
$userId
);
}
return $episodeAction;
}
} }

View File

@ -43,20 +43,18 @@ class EpisodeActionMapper extends QBMapper
* @param string $userId * @param string $userId
* @return EpisodeActionEntity|null * @return EpisodeActionEntity|null
*/ */
public function findByEpisodeIdentifier(string $episodeIdentifier, string $userId) : ?EpisodeActionEntity public function findByEpisodeUrl(string $episodeIdentifier, string $userId) : ?EpisodeActionEntity
{ {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
->where( ->where(
$qb->expr()->orX( $qb->expr()->eq('episode', $qb->createNamedParameter($episodeIdentifier))
$qb->expr()->eq('episode', $qb->createNamedParameter($episodeIdentifier)), )
$qb->expr()->eq('guid', $qb->createNamedParameter($episodeIdentifier)))
)
->andWhere( ->andWhere(
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId)) $qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
); );
try { try {
/** @var EpisodeActionEntity $episodeActionEntity */ /** @var EpisodeActionEntity $episodeActionEntity */
@ -66,5 +64,26 @@ class EpisodeActionMapper extends QBMapper
} }
} }
public function findByGuid(string $guid, string $userId) : ?EpisodeActionEntity
{
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('guid', $qb->createNamedParameter($guid))
)
->andWhere(
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId))
);
try {
/** @var EpisodeActionEntity $episodeActionEntity */
return $this->findEntity($qb);
} catch (DoesNotExistException|MultipleObjectsReturnedException|Exception $e) {
return null;
}
}
} }

View File

@ -28,17 +28,29 @@ class EpisodeActionRepository {
return $episodeActions; return $episodeActions;
} }
public function findByEpisodeIdentifier(string $identifier, string $userId): ?EpisodeAction { public function findByEpisodeUrl(string $episodeUrl, string $userId): ?EpisodeAction {
$episodeActionEntity = $this->episodeActionMapper->findByEpisodeIdentifier($identifier, $userId); $episodeActionEntity = $this->episodeActionMapper->findByEpisodeUrl($episodeUrl, $userId);
if ($episodeActionEntity === null) { if ($episodeActionEntity === null) {
return null; return null;
} }
return $this->mapEntityToEpisodeAction( return $this->mapEntityToEpisodeAction(
$episodeActionEntity $episodeActionEntity
); );
} }
public function findByGuid(string $guid, string $userId): ?EpisodeAction {
$episodeActionEntity = $this->episodeActionMapper->findByGuid($guid, $userId);
if ($episodeActionEntity === null) {
return null;
}
return $this->mapEntityToEpisodeAction(
$episodeActionEntity
);
}
private function mapEntityToEpisodeAction(EpisodeActionEntity $episodeActionEntity): EpisodeAction private function mapEntityToEpisodeAction(EpisodeActionEntity $episodeActionEntity): EpisodeAction
{ {

View File

@ -3,7 +3,9 @@ declare(strict_types=1);
namespace tests\Integration\Controller; namespace tests\Integration\Controller;
use OC\Security\SecureRandom; require_once __DIR__ . '/../../Helper/DatabaseTransaction.php';
require_once __DIR__ . '/../../Helper/Writer/TestWriter.php';
use OCA\GPodderSync\Controller\EpisodeActionController; use OCA\GPodderSync\Controller\EpisodeActionController;
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionSaver; use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionSaver;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity; use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity;
@ -12,7 +14,6 @@ use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter; use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter;
use OCP\AppFramework\App; use OCP\AppFramework\App;
use OCP\AppFramework\IAppContainer; use OCP\AppFramework\IAppContainer;
use OCP\IConfig;
use OCP\IRequest; use OCP\IRequest;
use Test\TestCase; use Test\TestCase;
use tests\Helper\DatabaseTransaction; use tests\Helper\DatabaseTransaction;

View File

@ -22,19 +22,13 @@ class EpisodeActionGuidMigrationTest extends TestCase
private IAppContainer $container; private IAppContainer $container;
/** private EpisodeActionWriter $episodeActionWriter;
* @var EpisodeActionWriter
*/
private $episodeActionWriter;
/** private EpisodeActionRepository $episodeActionRepository;
* @var EpisodeActionRepository
*/
private $episodeActionRepository;
public function setUp(): void { public function setUp(): void {
parent::setUp(); parent::setUp();
$app = new App('gpoddersync'); $app = new App('nextcloud-gpodder');
$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->episodeActionRepository = $this->container->get(EpisodeActionRepository::class);
@ -85,7 +79,7 @@ class EpisodeActionGuidMigrationTest extends TestCase
self::assertSame( self::assertSame(
$savedEpisodeActionEntity->getId(), $savedEpisodeActionEntity->getId(),
$this->episodeActionRepository->findByEpisodeIdentifier($episodeActionEntity->getEpisode(), self::USER_ID_0)->getId() $this->episodeActionRepository->findByEpisodeUrl($episodeActionEntity->getEpisode(), self::USER_ID_0)->getId()
); );
//update same episode action again this time with guid //update same episode action again this time with guid
@ -96,12 +90,12 @@ class EpisodeActionGuidMigrationTest extends TestCase
self::assertSame( self::assertSame(
$savedEpisodeActionEntityWithGuid->getId(), $savedEpisodeActionEntityWithGuid->getId(),
$this->episodeActionRepository->findByEpisodeIdentifier($episodeActionEntityWithGuid->getEpisode(), self::USER_ID_0)->getId() $this->episodeActionRepository->findByEpisodeUrl($episodeActionEntityWithGuid->getEpisode(), self::USER_ID_0)->getId()
); );
self::assertSame( self::assertSame(
$savedEpisodeActionEntityWithGuid->getId(), $savedEpisodeActionEntityWithGuid->getId(),
$this->episodeActionRepository->findByEpisodeIdentifier($episodeActionEntityWithGuid->getGuid(), self::USER_ID_0)->getId() $this->episodeActionRepository->findByGuid($episodeActionEntityWithGuid->getGuid(), self::USER_ID_0)->getId()
); );
} }

View File

@ -49,7 +49,7 @@ class EpisodeActionRepositoryTest extends \Test\TestCase
/** @var $episodeActionRepository EpisodeActionRepository */ /** @var $episodeActionRepository EpisodeActionRepository */
$episodeActionRepository = $this->container->get(EpisodeActionRepository::class); $episodeActionRepository = $this->container->get(EpisodeActionRepository::class);
$retrievedEpisodeActionEntity = $episodeActionRepository->findByEpisodeIdentifier($guid, self::USER_ID_0); $retrievedEpisodeActionEntity = $episodeActionRepository->findByGuid($guid, self::USER_ID_0);
self::assertSame('2021-08-22T23:58:56', $retrievedEpisodeActionEntity->getTimestamp()); self::assertSame('2021-08-22T23:58:56', $retrievedEpisodeActionEntity->getTimestamp());
} }

View File

@ -10,7 +10,7 @@ use Test\TestCase;
/** /**
* @group DB * @group DB
*/ */
class EpisodeActionSaverGuidBackwardCompatbilityTest extends TestCase class EpisodeActionSaverGuidBackwardCompatibilityTest extends TestCase
{ {
private const USER_ID_0 = "testuser0"; private const USER_ID_0 = "testuser0";

View File

@ -79,7 +79,7 @@ class TimestampMigrationTest extends TestCase
$trueCrimeEpisodeActionEntity->setGuid(uniqid("self::TEST_GUID_1234")); $trueCrimeEpisodeActionEntity->setGuid(uniqid("self::TEST_GUID_1234"));
$this->episodeActionWriter->save($trueCrimeEpisodeActionEntity); $this->episodeActionWriter->save($trueCrimeEpisodeActionEntity);
$episodeActionBeforeConversion = $this->episodeActionMapper->findByEpisodeIdentifier( $episodeActionBeforeConversion = $this->episodeActionMapper->findByGuid(
$scienceEpisodeActionEntity->getGuid(), $scienceEpisodeActionEntity->getGuid(),
self::ADMIN self::ADMIN
); );
@ -92,7 +92,7 @@ class TimestampMigrationTest extends TestCase
$timestampMigration = new TimestampMigration($this->dbConnection, $this->migrationConfig); $timestampMigration = new TimestampMigration($this->dbConnection, $this->migrationConfig);
$timestampMigration->run($this->createMock(SimpleOutput::class)); $timestampMigration->run($this->createMock(SimpleOutput::class));
$scienceEpisodeActionAfterConversion = $this->episodeActionMapper->findByEpisodeIdentifier( $scienceEpisodeActionAfterConversion = $this->episodeActionMapper->findByGuid(
$scienceEpisodeActionEntity->getGuid(), $scienceEpisodeActionEntity->getGuid(),
self::ADMIN self::ADMIN
); );
@ -101,7 +101,7 @@ class TimestampMigrationTest extends TestCase
$scienceEpisodeActionAfterConversion->getTimestampEpoch() $scienceEpisodeActionAfterConversion->getTimestampEpoch()
); );
$trueCrimeEpisodeActionAfterConversion = $this->episodeActionMapper->findByEpisodeIdentifier( $trueCrimeEpisodeActionAfterConversion = $this->episodeActionMapper->findByGuid(
$trueCrimeEpisodeActionEntity->getGuid(), $trueCrimeEpisodeActionEntity->getGuid(),
self::ADMIN self::ADMIN
); );