Add versioning to stored objects

This update introduces a versioning system to the stored objects in the ChillDocStoreBundle. The 'StoredObject' entity now includes several new methods, and maintains a collection of 'StoredObjectVersion' instances. Each time a 'StoredObject' is modified, a new version instance is created and added to the collection, ensuring a history of changes. Migration file for the addition of new database column included. Corresponding tests are also updated.
This commit is contained in:
2024-07-09 22:23:24 +02:00
parent 8a374864fa
commit 2b7ea4178b
4 changed files with 333 additions and 62 deletions

View File

@@ -16,9 +16,12 @@ use ChampsLibres\WopiLib\Contract\Entity\Document;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Random\RandomException;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
@@ -28,9 +31,12 @@ use Symfony\Component\Serializer\Annotation as Serializer;
*
* The property `$deleteAt` allow a deletion of the document after the given date. But this property should
* be set before the document is actually written by the StoredObjectManager.
*
* Each version is stored within a @see{StoredObjectVersion}, associated with this current's object. The creation
* of each new version should be done using the method @see{self::registerVersion}.
*/
#[ORM\Entity]
#[ORM\Table('chill_doc.stored_object')]
#[ORM\Table('stored_object', schema: 'chill_doc')]
#[AsyncFileExists(message: 'The file is not stored properly')]
class StoredObject implements Document, TrackCreationInterface
{
@@ -43,9 +49,11 @@ class StoredObject implements Document, TrackCreationInterface
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, name: 'datas')]
private array $datas = [];
#[Serializer\Groups(['write'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
private string $filename = '';
/**
* the prefix of each version.
*/
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
private string $prefix = '';
#[Serializer\Groups(['write'])]
#[ORM\Id]
@@ -53,25 +61,10 @@ class StoredObject implements Document, TrackCreationInterface
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
private ?int $id = null;
/**
* @var int[]
*/
#[Serializer\Groups(['write'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, name: 'iv')]
private array $iv = [];
#[Serializer\Groups(['write'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, name: 'key')]
private array $keyInfos = [];
#[Serializer\Groups(['write'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, name: 'title')]
#[ORM\Column(name: 'title', type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])]
private string $title = '';
#[Serializer\Groups(['write'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, name: 'type', options: ['default' => ''])]
private string $type = '';
#[Serializer\Groups(['write'])]
#[ORM\Column(type: 'uuid', unique: true)]
private UuidInterface $uuid;
@@ -94,6 +87,12 @@ class StoredObject implements Document, TrackCreationInterface
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
private string $generationErrors = '';
/**
* @var Collection<int, StoredObjectVersion>
*/
#[ORM\OneToMany(targetEntity: StoredObjectVersion::class, cascade: ['persist'], mappedBy: 'storedObject')]
private Collection $versions;
/**
* @param StoredObject::STATUS_* $status
*/
@@ -102,6 +101,8 @@ class StoredObject implements Document, TrackCreationInterface
private string $status = 'ready'
) {
$this->uuid = Uuid::uuid4();
$this->versions = new ArrayCollection();
$this->prefix = self::generatePrefix();
}
public function addGenerationTrial(): self
@@ -125,14 +126,32 @@ class StoredObject implements Document, TrackCreationInterface
return \DateTime::createFromImmutable($this->createdAt);
}
public function getCurrentVersion(): ?StoredObjectVersion
{
$maxVersion = null;
foreach ($this->versions as $v) {
if ($v->getVersion() > ($maxVersion?->getVersion() ?? -1)) {
$maxVersion = $v;
}
}
return $maxVersion;
}
public function getDatas(): array
{
return $this->datas;
}
public function getPrefix(): string
{
return $this->prefix;
}
public function getFilename(): string
{
return $this->filename;
return $this->getCurrentVersion()?->getFilename() ?? '';
}
public function getGenerationTrialsCounter(): int
@@ -145,14 +164,17 @@ class StoredObject implements Document, TrackCreationInterface
return $this->id;
}
/**
* @return list<int>
*/
public function getIv(): array
{
return $this->iv;
return $this->getCurrentVersion()?->getIv() ?? [];
}
public function getKeyInfos(): array
{
return $this->keyInfos;
return $this->getCurrentVersion()?->getKeyInfos() ?? [];
}
/**
@@ -171,14 +193,14 @@ class StoredObject implements Document, TrackCreationInterface
return $this->status;
}
public function getTitle()
public function getTitle(): string
{
return $this->title;
}
public function getType()
public function getType(): string
{
return $this->type;
return $this->getCurrentVersion()?->getType() ?? '';
}
public function getUuid(): UuidInterface
@@ -209,27 +231,6 @@ class StoredObject implements Document, TrackCreationInterface
return $this;
}
public function setFilename(?string $filename): self
{
$this->filename = (string) $filename;
return $this;
}
public function setIv(?array $iv): self
{
$this->iv = (array) $iv;
return $this;
}
public function setKeyInfos(?array $keyInfos): self
{
$this->keyInfos = (array) $keyInfos;
return $this;
}
/**
* @param StoredObject::STATUS_* $status
*/
@@ -247,18 +248,16 @@ class StoredObject implements Document, TrackCreationInterface
return $this;
}
public function setType(?string $type): self
{
$this->type = (string) $type;
return $this;
}
public function getTemplate(): ?DocGeneratorTemplate
{
return $this->template;
}
public function getVersions(): Collection
{
return $this->versions;
}
public function hasTemplate(): bool
{
return null !== $this->template;
@@ -314,6 +313,29 @@ class StoredObject implements Document, TrackCreationInterface
return $this;
}
public function registerVersion(
array $iv = [],
array $keyInfos = [],
string $type = '',
?string $filename = null,
): StoredObjectVersion {
$version = new StoredObjectVersion(
$this,
null === $this->getCurrentVersion() ? 0 : $this->getCurrentVersion()->getVersion() + 1,
$iv,
$keyInfos,
$type,
$filename
);
$this->versions->add($version);
return $version;
}
/**
* @deprecated
*/
public function saveHistory(): void
{
if ('' === $this->getFilename()) {
@@ -328,4 +350,13 @@ class StoredObject implements Document, TrackCreationInterface
'before' => (new \DateTimeImmutable('now'))->getTimestamp(),
];
}
public static function generatePrefix(): string
{
try {
return base_convert(bin2hex(random_bytes(8)), 16, 36);
} catch (RandomException $e) {
return uniqid(more_entropy: true);
}
}
}