wtf
All checks were successful
CI - Build Tonehaus Docker image / tonehaus-ci-build (push) Successful in 2m0s

This commit is contained in:
2025-11-28 02:00:11 +00:00
parent 1c98a634c3
commit dae8f3d999
35 changed files with 1510 additions and 82 deletions

View File

@@ -2,8 +2,11 @@
namespace App\Entity;
use App\Repository\AlbumRepository;
use App\Entity\AlbumTrack;
use App\Entity\User;
use App\Repository\AlbumRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
@@ -15,6 +18,10 @@ use Symfony\Component\Validator\Constraints as Assert;
#[ORM\HasLifecycleCallbacks]
class Album
{
#[ORM\OneToMany(mappedBy: 'album', targetEntity: AlbumTrack::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ORM\OrderBy(['discNumber' => 'ASC', 'trackNumber' => 'ASC'])]
private Collection $tracks;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
@@ -68,6 +75,11 @@ class Album
#[ORM\Column(type: 'datetime_immutable')]
private ?\DateTimeImmutable $updatedAt = null;
public function __construct()
{
$this->tracks = new ArrayCollection();
}
/**
* Initializes timestamps right before first persistence.
*/
@@ -324,6 +336,29 @@ class Album
'source' => $this->source,
];
}
/**
* @return Collection<int, AlbumTrack>
*/
public function getTracks(): Collection
{
return $this->tracks;
}
public function addTrack(AlbumTrack $track): void
{
if (!$this->tracks->contains($track)) {
$this->tracks->add($track);
$track->setAlbum($this);
}
}
public function removeTrack(AlbumTrack $track): void
{
if ($this->tracks->removeElement($track) && $track->getAlbum() === $this) {
$track->setAlbum(null);
}
}
}

140
src/Entity/AlbumTrack.php Normal file
View File

@@ -0,0 +1,140 @@
<?php
namespace App\Entity;
use App\Repository\AlbumTrackRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* AlbumTrack persists individual tracks fetched from Spotify.
*/
#[ORM\Entity(repositoryClass: AlbumTrackRepository::class)]
#[ORM\Table(name: 'album_tracks')]
#[ORM\UniqueConstraint(name: 'uniq_album_disc_track', columns: ['album_id', 'disc_number', 'track_number'])]
class AlbumTrack
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Album::class, inversedBy: 'tracks')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private ?Album $album = null;
#[ORM\Column(type: 'string', length: 64, nullable: true)]
private ?string $spotifyTrackId = null;
#[ORM\Column(type: 'integer')]
private int $discNumber = 1;
#[ORM\Column(type: 'integer')]
private int $trackNumber = 1;
#[ORM\Column(type: 'string', length: 512)]
private string $name = '';
#[ORM\Column(type: 'integer')]
private int $durationMs = 0;
#[ORM\Column(type: 'string', length: 1024, nullable: true)]
private ?string $previewUrl = null;
public function getId(): ?int
{
return $this->id;
}
public function getAlbum(): ?Album
{
return $this->album;
}
public function setAlbum(?Album $album): void
{
$this->album = $album;
}
public function getSpotifyTrackId(): ?string
{
return $this->spotifyTrackId;
}
public function setSpotifyTrackId(?string $spotifyTrackId): void
{
$this->spotifyTrackId = $spotifyTrackId;
}
public function getDiscNumber(): int
{
return $this->discNumber;
}
public function setDiscNumber(int $discNumber): void
{
$this->discNumber = max(1, $discNumber);
}
public function getTrackNumber(): int
{
return $this->trackNumber;
}
public function setTrackNumber(int $trackNumber): void
{
$this->trackNumber = max(1, $trackNumber);
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function getDurationMs(): int
{
return $this->durationMs;
}
public function setDurationMs(int $durationMs): void
{
$this->durationMs = max(0, $durationMs);
}
public function getPreviewUrl(): ?string
{
return $this->previewUrl;
}
public function setPreviewUrl(?string $previewUrl): void
{
$this->previewUrl = $previewUrl;
}
/**
* Normalizes the track for template rendering.
*
* @return array{disc:int,track:int,name:string,duration_label:string,duration_seconds:int,preview_url:?string}
*/
public function toTemplateArray(): array
{
$seconds = (int) floor($this->durationMs / 1000);
$minutes = intdiv($seconds, 60);
$remainingSeconds = $seconds % 60;
return [
'disc' => $this->discNumber,
'track' => $this->trackNumber,
'name' => $this->name,
'duration_label' => sprintf('%d:%02d', $minutes, $remainingSeconds),
'duration_seconds' => $seconds,
'preview_url' => $this->previewUrl,
];
}
}