optimizations
This commit is contained in:
283
app/Services/NovaCards/NovaCardDraftService.php
Normal file
283
app/Services/NovaCards/NovaCardDraftService.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\NovaCards;
|
||||
|
||||
use App\Events\NovaCards\NovaCardAutosaved;
|
||||
use App\Events\NovaCards\NovaCardCreated;
|
||||
use App\Events\NovaCards\NovaCardTemplateSelected;
|
||||
use App\Models\NovaCard;
|
||||
use App\Models\NovaCardCategory;
|
||||
use App\Models\NovaCardTemplate;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class NovaCardDraftService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NovaCardTagService $tagService,
|
||||
private readonly NovaCardProjectNormalizer $normalizer,
|
||||
private readonly NovaCardVersionService $versions,
|
||||
) {
|
||||
}
|
||||
|
||||
public function createDraft(User $user, array $attributes = []): NovaCard
|
||||
{
|
||||
$template = $this->resolveTemplate(Arr::get($attributes, 'template_id'));
|
||||
$category = $this->resolveCategory(Arr::get($attributes, 'category_id'));
|
||||
$title = trim((string) Arr::get($attributes, 'title', 'Untitled card'));
|
||||
$quote = trim((string) Arr::get($attributes, 'quote_text', 'Your next quote starts here.'));
|
||||
$project = $this->normalizer->upgradeToV2(null, $template, $attributes);
|
||||
$topLevel = $this->normalizer->syncTopLevelAttributes($project);
|
||||
$originalCardId = Arr::get($attributes, 'original_card_id');
|
||||
$rootCardId = Arr::get($attributes, 'root_card_id', $originalCardId);
|
||||
|
||||
$card = NovaCard::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'category_id' => $category?->id,
|
||||
'template_id' => $template?->id,
|
||||
'title' => Str::limit($topLevel['title'] ?: $title, (int) config('nova_cards.validation.title_max', 120), ''),
|
||||
'slug' => $this->generateUniqueSlug($title),
|
||||
'quote_text' => Str::limit($topLevel['quote_text'] ?: $quote, (int) config('nova_cards.validation.quote_max', 420), ''),
|
||||
'quote_author' => $topLevel['quote_author'],
|
||||
'quote_source' => $topLevel['quote_source'],
|
||||
'description' => Arr::get($attributes, 'description'),
|
||||
'format' => $this->resolveFormat((string) Arr::get($attributes, 'format', NovaCard::FORMAT_SQUARE)),
|
||||
'project_json' => $project,
|
||||
'schema_version' => (int) $topLevel['schema_version'],
|
||||
'visibility' => NovaCard::VISIBILITY_PRIVATE,
|
||||
'status' => NovaCard::STATUS_DRAFT,
|
||||
'moderation_status' => NovaCard::MOD_PENDING,
|
||||
'background_type' => $topLevel['background_type'],
|
||||
'background_image_id' => $topLevel['background_image_id'],
|
||||
'allow_download' => true,
|
||||
'allow_remix' => (bool) Arr::get($attributes, 'allow_remix', true),
|
||||
'allow_background_reuse' => (bool) Arr::get($attributes, 'allow_background_reuse', false),
|
||||
'allow_export' => (bool) Arr::get($attributes, 'allow_export', true),
|
||||
'style_family' => Arr::get($attributes, 'style_family'),
|
||||
'palette_family' => Arr::get($attributes, 'palette_family'),
|
||||
'original_card_id' => $originalCardId,
|
||||
'root_card_id' => $rootCardId,
|
||||
]);
|
||||
|
||||
$this->tagService->syncTags($card, Arr::wrap(Arr::get($attributes, 'tags', [])));
|
||||
$this->versions->snapshot($card->fresh(['template']), $user, $originalCardId ? 'Remix draft created' : 'Initial draft', true);
|
||||
|
||||
event(new NovaCardCreated($card->fresh()->load(['category', 'template', 'tags'])));
|
||||
|
||||
if ($card->template_id !== null) {
|
||||
event(new NovaCardTemplateSelected($card->fresh()->load(['category', 'template', 'tags']), null, (int) $card->template_id));
|
||||
}
|
||||
|
||||
return $card->load(['category', 'template', 'tags']);
|
||||
}
|
||||
|
||||
public function autosave(NovaCard $card, array $payload): NovaCard
|
||||
{
|
||||
$currentProject = $this->normalizer->upgradeToV2(is_array($card->project_json) ? $card->project_json : [], $card->template, [], $card);
|
||||
$template = $this->resolveTemplateId($payload, $card)
|
||||
? NovaCardTemplate::query()->find($this->resolveTemplateId($payload, $card))
|
||||
: $card->template;
|
||||
$projectPatch = is_array(Arr::get($payload, 'project_json')) ? Arr::get($payload, 'project_json') : [];
|
||||
$normalizedProject = $this->normalizer->upgradeToV2(
|
||||
array_replace_recursive($currentProject, $projectPatch),
|
||||
$template,
|
||||
array_merge($payload, [
|
||||
'title' => Arr::get($payload, 'title', $card->title),
|
||||
'quote_text' => Arr::get($payload, 'quote_text', $card->quote_text),
|
||||
'quote_author' => Arr::get($payload, 'quote_author', $card->quote_author),
|
||||
'quote_source' => Arr::get($payload, 'quote_source', $card->quote_source),
|
||||
'background_type' => Arr::get($payload, 'background_type', $card->background_type),
|
||||
'background_image_id' => Arr::get($payload, 'background_image_id', $card->background_image_id),
|
||||
'original_card_id' => $card->original_card_id,
|
||||
'root_card_id' => $card->root_card_id,
|
||||
]),
|
||||
$card,
|
||||
);
|
||||
$topLevel = $this->normalizer->syncTopLevelAttributes($normalizedProject);
|
||||
|
||||
if (array_key_exists('title', $payload)) {
|
||||
$topLevel['title'] = trim((string) $payload['title']);
|
||||
}
|
||||
|
||||
if (array_key_exists('quote_text', $payload)) {
|
||||
$topLevel['quote_text'] = trim((string) $payload['quote_text']);
|
||||
}
|
||||
|
||||
if (array_key_exists('quote_author', $payload)) {
|
||||
$topLevel['quote_author'] = (($value = trim((string) $payload['quote_author'])) !== '') ? $value : null;
|
||||
}
|
||||
|
||||
if (array_key_exists('quote_source', $payload)) {
|
||||
$topLevel['quote_source'] = (($value = trim((string) $payload['quote_source'])) !== '') ? $value : null;
|
||||
}
|
||||
|
||||
$previousTemplateId = $card->template_id ? (int) $card->template_id : null;
|
||||
|
||||
$card->fill([
|
||||
'title' => Str::limit($topLevel['title'], (int) config('nova_cards.validation.title_max', 120), ''),
|
||||
'quote_text' => Str::limit($topLevel['quote_text'], (int) config('nova_cards.validation.quote_max', 420), ''),
|
||||
'quote_author' => $topLevel['quote_author'],
|
||||
'quote_source' => $topLevel['quote_source'],
|
||||
'description' => Arr::get($payload, 'description', $card->description),
|
||||
'format' => $this->resolveFormat((string) Arr::get($payload, 'format', $card->format)),
|
||||
'project_json' => $normalizedProject,
|
||||
'schema_version' => (int) $topLevel['schema_version'],
|
||||
'background_type' => $topLevel['background_type'],
|
||||
'background_image_id' => $topLevel['background_image_id'],
|
||||
'template_id' => $this->resolveTemplateId($payload, $card),
|
||||
'category_id' => $this->resolveCategoryId($payload, $card),
|
||||
'visibility' => Arr::get($payload, 'visibility', $card->visibility),
|
||||
'allow_download' => (bool) Arr::get($payload, 'allow_download', $card->allow_download),
|
||||
'allow_remix' => (bool) Arr::get($payload, 'allow_remix', $card->allow_remix),
|
||||
'allow_background_reuse' => (bool) Arr::get($payload, 'allow_background_reuse', $card->allow_background_reuse ?? false),
|
||||
'allow_export' => (bool) Arr::get($payload, 'allow_export', $card->allow_export ?? true),
|
||||
'style_family' => Arr::get($payload, 'style_family', $card->style_family),
|
||||
'palette_family' => Arr::get($payload, 'palette_family', $card->palette_family),
|
||||
'editor_mode_last_used' => Arr::get($payload, 'editor_mode_last_used', $card->editor_mode_last_used),
|
||||
]);
|
||||
|
||||
if ($card->isDirty('title')) {
|
||||
$card->slug = $this->generateUniqueSlug($card->title, $card->id);
|
||||
}
|
||||
|
||||
$card->save();
|
||||
$changes = $card->getChanges();
|
||||
|
||||
if (array_key_exists('tags', $payload)) {
|
||||
$this->tagService->syncTags($card, Arr::wrap(Arr::get($payload, 'tags', [])));
|
||||
$changes['tags'] = true;
|
||||
}
|
||||
|
||||
if ($changes !== []) {
|
||||
$fresh = $card->fresh()->load(['category', 'template', 'tags']);
|
||||
$this->versions->snapshot($fresh->loadMissing('template'), $fresh->user, Arr::get($payload, 'version_label'));
|
||||
event(new NovaCardAutosaved($fresh, array_keys($changes)));
|
||||
|
||||
$currentTemplateId = $fresh->template_id ? (int) $fresh->template_id : null;
|
||||
if ($currentTemplateId !== $previousTemplateId && $currentTemplateId !== null) {
|
||||
event(new NovaCardTemplateSelected($fresh, $previousTemplateId, $currentTemplateId));
|
||||
}
|
||||
}
|
||||
|
||||
return $card->refresh()->load(['category', 'template', 'tags']);
|
||||
}
|
||||
|
||||
public function createRemix(User $user, NovaCard $source, array $attributes = []): NovaCard
|
||||
{
|
||||
$baseProject = $this->normalizer->normalizeForCard($source->loadMissing('template'));
|
||||
$payload = array_merge([
|
||||
'title' => 'Remix of ' . $source->title,
|
||||
'quote_text' => $source->quote_text,
|
||||
'quote_author' => $source->quote_author,
|
||||
'quote_source' => $source->quote_source,
|
||||
'description' => $source->description,
|
||||
'format' => $source->format,
|
||||
'background_type' => $source->background_type,
|
||||
'background_image_id' => $source->background_image_id,
|
||||
'template_id' => $source->template_id,
|
||||
'category_id' => $source->category_id,
|
||||
'tags' => $source->tags->pluck('name')->all(),
|
||||
'project_json' => $baseProject,
|
||||
'original_card_id' => $source->id,
|
||||
'root_card_id' => $source->root_card_id ?: $source->id,
|
||||
], $attributes);
|
||||
|
||||
return $this->createDraft($user, $payload);
|
||||
}
|
||||
|
||||
public function createDuplicate(User $user, NovaCard $source, array $attributes = []): NovaCard
|
||||
{
|
||||
abort_unless($source->isOwnedBy($user), 403);
|
||||
|
||||
$baseProject = $this->normalizer->normalizeForCard($source->loadMissing('template'));
|
||||
$payload = array_merge([
|
||||
'title' => 'Copy of ' . $source->title,
|
||||
'quote_text' => $source->quote_text,
|
||||
'quote_author' => $source->quote_author,
|
||||
'quote_source' => $source->quote_source,
|
||||
'description' => $source->description,
|
||||
'format' => $source->format,
|
||||
'background_type' => $source->background_type,
|
||||
'background_image_id' => $source->background_image_id,
|
||||
'template_id' => $source->template_id,
|
||||
'category_id' => $source->category_id,
|
||||
'tags' => $source->tags->pluck('name')->all(),
|
||||
'project_json' => $baseProject,
|
||||
'allow_remix' => $source->allow_remix,
|
||||
], $attributes);
|
||||
|
||||
return $this->createDraft($user, $payload);
|
||||
}
|
||||
|
||||
private function buildProjectPayload(?NovaCardTemplate $template, array $attributes): array
|
||||
{
|
||||
return $this->normalizer->normalize(null, $template, $attributes);
|
||||
}
|
||||
|
||||
private function generateUniqueSlug(string $title, ?int $ignoreId = null): string
|
||||
{
|
||||
$base = Str::slug($title);
|
||||
if ($base === '') {
|
||||
$base = 'nova-card';
|
||||
}
|
||||
|
||||
$slug = $base;
|
||||
$suffix = 2;
|
||||
|
||||
while (NovaCard::query()
|
||||
->when($ignoreId !== null, fn ($query) => $query->where('id', '!=', $ignoreId))
|
||||
->where('slug', $slug)
|
||||
->exists()) {
|
||||
$slug = $base . '-' . $suffix;
|
||||
$suffix++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
private function resolveTemplate(mixed $templateId): ?NovaCardTemplate
|
||||
{
|
||||
if ($templateId) {
|
||||
return NovaCardTemplate::query()->find($templateId);
|
||||
}
|
||||
|
||||
return NovaCardTemplate::query()->where('active', true)->orderBy('order_num')->first();
|
||||
}
|
||||
|
||||
private function resolveCategory(mixed $categoryId): ?NovaCardCategory
|
||||
{
|
||||
if ($categoryId) {
|
||||
return NovaCardCategory::query()->find($categoryId);
|
||||
}
|
||||
|
||||
return NovaCardCategory::query()->where('active', true)->orderBy('order_num')->first();
|
||||
}
|
||||
|
||||
private function resolveFormat(string $format): string
|
||||
{
|
||||
$formats = array_keys((array) config('nova_cards.formats', []));
|
||||
|
||||
return in_array($format, $formats, true) ? $format : NovaCard::FORMAT_SQUARE;
|
||||
}
|
||||
|
||||
private function resolveTemplateId(array $payload, NovaCard $card): ?int
|
||||
{
|
||||
if (! array_key_exists('template_id', $payload)) {
|
||||
return $card->template_id;
|
||||
}
|
||||
|
||||
return $this->resolveTemplate(Arr::get($payload, 'template_id'))?->id;
|
||||
}
|
||||
|
||||
private function resolveCategoryId(array $payload, NovaCard $card): ?int
|
||||
{
|
||||
if (! array_key_exists('category_id', $payload)) {
|
||||
return $card->category_id;
|
||||
}
|
||||
|
||||
return $this->resolveCategory(Arr::get($payload, 'category_id'))?->id;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user