Implement creator studio and upload updates
This commit is contained in:
@@ -34,6 +34,9 @@ class SaveNovaCardDraftRequest extends FormRequest
|
||||
'allow_download' => ['sometimes', 'boolean'],
|
||||
'allow_remix' => ['sometimes', 'boolean'],
|
||||
'editor_mode_last_used' => ['sometimes', Rule::in(['quick', 'full'])],
|
||||
'publish_mode' => ['sometimes', Rule::in(['now', 'schedule'])],
|
||||
'scheduled_for' => ['sometimes', 'nullable', 'date'],
|
||||
'scheduling_timezone' => ['sometimes', 'nullable', 'string', 'max:64'],
|
||||
'tags' => ['sometimes', 'array', 'max:' . (int) ($validation['max_tags'] ?? 8)],
|
||||
'tags.*' => ['string', 'min:2', 'max:32'],
|
||||
'project_json' => ['sometimes', 'array'],
|
||||
@@ -43,6 +46,9 @@ class SaveNovaCardDraftRequest extends FormRequest
|
||||
'project_json.text_blocks.*.type' => ['sometimes', Rule::in(['title', 'quote', 'author', 'source', 'body', 'caption'])],
|
||||
'project_json.text_blocks.*.text' => ['sometimes', 'nullable', 'string', 'max:' . (int) ($validation['quote_max'] ?? 420)],
|
||||
'project_json.text_blocks.*.enabled' => ['sometimes', 'boolean'],
|
||||
'project_json.text_blocks.*.pos_x' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'project_json.text_blocks.*.pos_y' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'project_json.text_blocks.*.pos_width' => ['sometimes', 'nullable', 'numeric', 'min:1', 'max:100'],
|
||||
'project_json.assets.pack_ids' => ['sometimes', 'array'],
|
||||
'project_json.assets.pack_ids.*' => ['integer'],
|
||||
'project_json.assets.template_pack_ids' => ['sometimes', 'array'],
|
||||
@@ -57,7 +63,13 @@ class SaveNovaCardDraftRequest extends FormRequest
|
||||
'project_json.layout.padding' => ['sometimes', Rule::in((array) ($validation['allowed_padding_presets'] ?? []))],
|
||||
'project_json.layout.max_width' => ['sometimes', Rule::in((array) ($validation['allowed_max_widths'] ?? []))],
|
||||
'project_json.typography.font_preset' => ['sometimes', Rule::in(array_keys((array) config('nova_cards.font_presets', [])))],
|
||||
'project_json.typography.quote_size' => ['sometimes', 'integer', 'min:24', 'max:160'],
|
||||
'project_json.typography.quote_size' => ['sometimes', 'integer', 'min:10', 'max:160'],
|
||||
'project_json.typography.quote_width' => ['sometimes', 'nullable', 'integer', 'min:30', 'max:100'],
|
||||
'project_json.typography.text_opacity' => ['sometimes', 'nullable', 'integer', 'min:10', 'max:100'],
|
||||
'project_json.decorations' => ['sometimes', 'array'],
|
||||
'project_json.decorations.*.pos_x' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'project_json.decorations.*.pos_y' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'project_json.decorations.*.opacity' => ['sometimes', 'nullable', 'integer', 'min:10', 'max:100'],
|
||||
'project_json.typography.author_size' => ['sometimes', 'integer', 'min:12', 'max:72'],
|
||||
'project_json.typography.letter_spacing' => ['sometimes', 'integer', 'min:-2', 'max:12'],
|
||||
'project_json.typography.line_height' => ['sometimes', 'numeric', 'min:0.9', 'max:1.8'],
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace App\Http\Requests\NovaCards;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UploadNovaCardBackgroundRequest extends FormRequest
|
||||
{
|
||||
@@ -26,22 +25,56 @@ class UploadNovaCardBackgroundRequest extends FormRequest
|
||||
'bail',
|
||||
'required',
|
||||
'file',
|
||||
static function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if (! $value instanceof UploadedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $value->getRealPath() ?: $value->getPathname();
|
||||
|
||||
if (! $value->isValid() || ! is_string($path) || trim($path) === '') {
|
||||
$fail('The ' . $attribute . ' upload is invalid.');
|
||||
}
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$this->validateUpload($attribute, $value, $fail);
|
||||
},
|
||||
'image',
|
||||
'mimes:jpeg,jpg,png,webp',
|
||||
'max:' . $maxKilobytes,
|
||||
Rule::dimensions()->minWidth(480)->minHeight(480),
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
$this->validateMinimumDimensions($attribute, $value, $fail, 480, 480);
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function validateUpload(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if (! $value instanceof UploadedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $value->getRealPath() ?: $value->getPathname();
|
||||
|
||||
if (! $value->isValid() || ! is_string($path) || trim($path) === '' || ! is_readable($path)) {
|
||||
$fail('The ' . $attribute . ' upload is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
private function validateMinimumDimensions(string $attribute, mixed $value, Closure $fail, int $minWidth, int $minHeight): void
|
||||
{
|
||||
if (! $value instanceof UploadedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $value->getRealPath() ?: $value->getPathname();
|
||||
|
||||
if (! is_string($path) || trim($path) === '' || ! is_readable($path)) {
|
||||
$fail('The ' . $attribute . ' upload is invalid.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$binary = @file_get_contents($path);
|
||||
if ($binary === false || $binary === '') {
|
||||
$fail('The ' . $attribute . ' upload is invalid.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$dimensions = @getimagesizefromstring($binary);
|
||||
if (! is_array($dimensions) || ($dimensions[0] ?? 0) < $minWidth || ($dimensions[1] ?? 0) < $minHeight) {
|
||||
$fail(sprintf('The %s must be at least %dx%d pixels.', $attribute, $minWidth, $minHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,36 @@ final class UploadFinishRequest extends FormRequest
|
||||
$this->denyAsNotFound();
|
||||
}
|
||||
|
||||
$archiveSessionId = (string) $this->input('archive_session_id');
|
||||
if ($archiveSessionId !== '') {
|
||||
$archiveSession = $sessions->get($archiveSessionId);
|
||||
if (! $archiveSession || $archiveSession->userId !== $user->id) {
|
||||
$this->logUnauthorized('archive_session_not_owned_or_missing');
|
||||
$this->denyAsNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
$additionalScreenshotSessions = $this->input('additional_screenshot_sessions', []);
|
||||
if (is_array($additionalScreenshotSessions)) {
|
||||
foreach ($additionalScreenshotSessions as $index => $payload) {
|
||||
$screenshotSessionId = (string) data_get($payload, 'session_id', '');
|
||||
if ($screenshotSessionId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$screenshotSession = $sessions->get($screenshotSessionId);
|
||||
if (! $screenshotSession || $screenshotSession->userId !== $user->id) {
|
||||
$this->logUnauthorized('additional_screenshot_session_not_owned_or_missing');
|
||||
logger()->warning('Upload finish additional screenshot session rejected', [
|
||||
'index' => $index,
|
||||
'session_id' => $screenshotSessionId,
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
$this->denyAsNotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$artwork = Artwork::query()->find($artworkId);
|
||||
if (! $artwork || (int) $artwork->user_id !== (int) $user->id) {
|
||||
$this->logUnauthorized('artwork_not_owned_or_missing');
|
||||
@@ -79,6 +109,11 @@ final class UploadFinishRequest extends FormRequest
|
||||
'artwork_id' => 'required|integer',
|
||||
'upload_token' => 'nullable|string|min:40|max:200',
|
||||
'file_name' => 'nullable|string|max:255',
|
||||
'archive_session_id' => 'nullable|uuid|different:session_id',
|
||||
'archive_file_name' => 'nullable|string|max:255',
|
||||
'additional_screenshot_sessions' => 'nullable|array|max:4',
|
||||
'additional_screenshot_sessions.*.session_id' => 'required|uuid|distinct|different:session_id|different:archive_session_id',
|
||||
'additional_screenshot_sessions.*.file_name' => 'nullable|string|max:255',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user