Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Artworks;
final class ArtworkDraftResult
{
public function __construct(
public readonly int $artworkId,
public readonly string $status
) {
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Uploads;
final class UploadChunkResult
{
public function __construct(
public readonly string $sessionId,
public readonly string $status,
public readonly int $receivedBytes,
public readonly int $totalBytes,
public readonly int $progress
) {
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Uploads;
final class UploadInitResult
{
public function __construct(
public readonly string $sessionId,
public readonly string $token,
public readonly string $status
) {
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Uploads;
final class UploadScanResult
{
public function __construct(
public readonly bool $ok,
public readonly string $reason
) {
}
public static function clean(): self
{
return new self(true, '');
}
public static function infected(string $reason): self
{
return new self(false, $reason);
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Uploads;
use Carbon\CarbonImmutable;
final class UploadSessionData
{
public function __construct(
public readonly string $id,
public readonly int $userId,
public readonly string $tempPath,
public readonly string $status,
public readonly string $ip,
public readonly CarbonImmutable $createdAt,
public readonly int $progress,
public readonly ?string $failureReason
) {
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Uploads;
final class UploadStoredFile
{
public function __construct(
public readonly string $path,
public readonly int $size,
public readonly string $extension
) {
}
public static function fromPath(string $path): self
{
$size = is_file($path) ? (int) filesize($path) : 0;
$extension = (string) pathinfo($path, PATHINFO_EXTENSION);
return new self($path, $size, $extension);
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Uploads;
final class UploadValidatedFile
{
public function __construct(
public readonly UploadValidationResult $validation,
public readonly ?string $hash
) {
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Uploads;
final class UploadValidationResult
{
public function __construct(
public readonly bool $ok,
public readonly string $reason,
public readonly ?int $width,
public readonly ?int $height,
public readonly ?string $mime,
public readonly ?int $size
) {
}
public static function ok(int $width, int $height, string $mime, int $size): self
{
return new self(true, '', $width, $height, $mime, $size);
}
public static function fail(string $reason, ?int $width = null, ?int $height = null, ?string $mime = null, ?int $size = null): self
{
return new self(false, $reason, $width, $height, $mime, $size);
}
}

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace App\DTOs;
/**
* Lightweight value object representing a user's recommendation preference profile.
*
* Built by UserPreferenceBuilder from signals:
* - favourited artworks (+3)
* - awards given (+5)
* - creator follows (+2 for their tags)
* - own uploads (category bias)
*
* Cached in `user_reco_profiles` with a configurable TTL (default 6 hours).
*/
final class UserRecoProfileDTO
{
/**
* @param array<int, string> $topTagSlugs Top tag slugs by weighted score (up to 20)
* @param array<int, string> $topCategorySlugs Top category slugs (up to 5)
* @param array<int, int> $strongCreatorIds Followed creator user IDs (up to 50)
* @param array<string, float> $tagWeights Tag slug normalised weight (01)
* @param array<string, float> $categoryWeights Category slug normalised weight
* @param array<int, string> $dislikedTagSlugs Future: blocked/hidden tag slugs
*/
public function __construct(
public readonly array $topTagSlugs = [],
public readonly array $topCategorySlugs = [],
public readonly array $strongCreatorIds = [],
public readonly array $tagWeights = [],
public readonly array $categoryWeights = [],
public readonly array $dislikedTagSlugs = [],
) {}
/**
* True if the user has enough signals to drive personalised recommendations.
*/
public function hasSignals(): bool
{
return $this->topTagSlugs !== [] || $this->strongCreatorIds !== [];
}
/**
* Returns the normalised tag weight for a given slug (0.0 if unknown).
*/
public function tagWeight(string $slug): float
{
return (float) ($this->tagWeights[$slug] ?? 0.0);
}
/**
* Returns true when the creator is in the user's strong-follow list.
*/
public function followsCreator(int $userId): bool
{
return in_array($userId, $this->strongCreatorIds, true);
}
/**
* Serialise for storage in the DB / Redis cache.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'top_tags' => $this->topTagSlugs,
'top_categories' => $this->topCategorySlugs,
'strong_creators' => $this->strongCreatorIds,
'tag_weights' => $this->tagWeights,
'category_weights' => $this->categoryWeights,
'disliked_tags' => $this->dislikedTagSlugs,
];
}
/**
* Re-hydrate from a stored array (e.g. from the DB JSON column).
*
* @param array<string, mixed> $data
*/
public static function fromArray(array $data): self
{
return new self(
topTagSlugs: (array) ($data['top_tags'] ?? []),
topCategorySlugs: (array) ($data['top_categories'] ?? []),
strongCreatorIds: array_map('intval', (array) ($data['strong_creators'] ?? [])),
tagWeights: array_map('floatval', (array) ($data['tag_weights'] ?? [])),
categoryWeights: array_map('floatval', (array) ($data['category_weights'] ?? [])),
dislikedTagSlugs: (array) ($data['disliked_tags'] ?? []),
);
}
}