optimizations
This commit is contained in:
235
app/Services/NovaCards/NovaCardCreatorPresetService.php
Normal file
235
app/Services/NovaCards/NovaCardCreatorPresetService.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\NovaCards;
|
||||
|
||||
use App\Models\NovaCard;
|
||||
use App\Models\NovaCardCreatorPreset;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class NovaCardCreatorPresetService
|
||||
{
|
||||
/** Maximum presets per user across all types. */
|
||||
public const MAX_TOTAL = 30;
|
||||
|
||||
/** Maximum presets per type per user. */
|
||||
public const MAX_PER_TYPE = 8;
|
||||
|
||||
public function listForUser(User $user, ?string $presetType = null): Collection
|
||||
{
|
||||
return NovaCardCreatorPreset::query()
|
||||
->where('user_id', $user->id)
|
||||
->when($presetType !== null, fn ($q) => $q->where('preset_type', $presetType))
|
||||
->orderByDesc('is_default')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function create(User $user, array $data): NovaCardCreatorPreset
|
||||
{
|
||||
$type = (string) Arr::get($data, 'preset_type', NovaCardCreatorPreset::TYPE_STYLE);
|
||||
|
||||
// Enforce per-type limit.
|
||||
$typeCount = NovaCardCreatorPreset::query()
|
||||
->where('user_id', $user->id)
|
||||
->where('preset_type', $type)
|
||||
->count();
|
||||
|
||||
if ($typeCount >= self::MAX_PER_TYPE) {
|
||||
abort(422, 'Maximum number of ' . $type . ' presets reached (' . self::MAX_PER_TYPE . ').');
|
||||
}
|
||||
|
||||
// Enforce total limit.
|
||||
$totalCount = NovaCardCreatorPreset::query()->where('user_id', $user->id)->count();
|
||||
if ($totalCount >= self::MAX_TOTAL) {
|
||||
abort(422, 'Maximum total presets reached (' . self::MAX_TOTAL . ').');
|
||||
}
|
||||
|
||||
$preset = NovaCardCreatorPreset::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'name' => (string) Arr::get($data, 'name', 'My preset'),
|
||||
'preset_type' => $type,
|
||||
'config_json' => $this->sanitizeConfig((array) Arr::get($data, 'config_json', []), $type),
|
||||
'is_default' => false,
|
||||
]);
|
||||
|
||||
if ((bool) Arr::get($data, 'is_default', false)) {
|
||||
$this->setDefault($user, $preset);
|
||||
}
|
||||
|
||||
return $preset->refresh();
|
||||
}
|
||||
|
||||
public function update(User $user, NovaCardCreatorPreset $preset, array $data): NovaCardCreatorPreset
|
||||
{
|
||||
$this->authorizeOwner($user, $preset);
|
||||
|
||||
$preset->fill([
|
||||
'name' => (string) Arr::get($data, 'name', $preset->name),
|
||||
'config_json' => $this->sanitizeConfig(
|
||||
(array) Arr::get($data, 'config_json', $preset->config_json ?? []),
|
||||
$preset->preset_type,
|
||||
),
|
||||
]);
|
||||
$preset->save();
|
||||
|
||||
if (array_key_exists('is_default', $data) && (bool) $data['is_default']) {
|
||||
$this->setDefault($user, $preset);
|
||||
}
|
||||
|
||||
return $preset->refresh();
|
||||
}
|
||||
|
||||
public function delete(User $user, NovaCardCreatorPreset $preset): void
|
||||
{
|
||||
$this->authorizeOwner($user, $preset);
|
||||
$preset->delete();
|
||||
}
|
||||
|
||||
public function setDefault(User $user, NovaCardCreatorPreset|int $preset): void
|
||||
{
|
||||
if (is_int($preset)) {
|
||||
$preset = NovaCardCreatorPreset::query()->findOrFail($preset);
|
||||
}
|
||||
|
||||
$this->authorizeOwner($user, $preset);
|
||||
|
||||
// Clear existing defaults of the same type before setting new one.
|
||||
NovaCardCreatorPreset::query()
|
||||
->where('user_id', $user->id)
|
||||
->where('preset_type', $preset->preset_type)
|
||||
->where('id', '!=', $preset->id)
|
||||
->update(['is_default' => false]);
|
||||
|
||||
$preset->update(['is_default' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture a preset from a published or saved card project JSON.
|
||||
* Extracts only the fields relevant to the given preset type.
|
||||
*/
|
||||
public function captureFromCard(User $user, NovaCard $card, string $presetName, string $presetType): NovaCardCreatorPreset
|
||||
{
|
||||
$project = is_array($card->project_json) ? $card->project_json : [];
|
||||
$config = $this->extractFromProject($project, $presetType);
|
||||
|
||||
return $this->create($user, [
|
||||
'name' => $presetName,
|
||||
'preset_type' => $presetType,
|
||||
'config_json' => $config,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a preset config on top of the given project patch array.
|
||||
* Returns the merged patch to be passed into the draft autosave flow.
|
||||
*/
|
||||
public function applyToProjectPatch(NovaCardCreatorPreset $preset, array|NovaCard $currentProject): array
|
||||
{
|
||||
if ($currentProject instanceof NovaCard) {
|
||||
$currentProject = is_array($currentProject->project_json) ? $currentProject->project_json : [];
|
||||
}
|
||||
|
||||
$config = $preset->config_json ?? [];
|
||||
|
||||
return match ($preset->preset_type) {
|
||||
NovaCardCreatorPreset::TYPE_TYPOGRAPHY => [
|
||||
'typography' => array_merge(
|
||||
(array) Arr::get($currentProject, 'typography', []),
|
||||
(array) (Arr::get($config, 'typography', $config)),
|
||||
),
|
||||
],
|
||||
NovaCardCreatorPreset::TYPE_LAYOUT => [
|
||||
'layout' => array_merge(
|
||||
(array) Arr::get($currentProject, 'layout', []),
|
||||
(array) (Arr::get($config, 'layout', $config)),
|
||||
),
|
||||
],
|
||||
NovaCardCreatorPreset::TYPE_BACKGROUND => [
|
||||
'background' => array_merge(
|
||||
(array) Arr::get($currentProject, 'background', []),
|
||||
(array) (Arr::get($config, 'background', $config)),
|
||||
),
|
||||
],
|
||||
NovaCardCreatorPreset::TYPE_STYLE => array_filter([
|
||||
'typography' => isset($config['typography'])
|
||||
? array_merge((array) Arr::get($currentProject, 'typography', []), (array) $config['typography'])
|
||||
: null,
|
||||
'background' => isset($config['background'])
|
||||
? array_merge((array) Arr::get($currentProject, 'background', []), (array) $config['background'])
|
||||
: null,
|
||||
'effects' => isset($config['effects'])
|
||||
? array_merge((array) Arr::get($currentProject, 'effects', []), (array) $config['effects'])
|
||||
: null,
|
||||
]),
|
||||
NovaCardCreatorPreset::TYPE_STARTER => $config,
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
|
||||
private function extractFromProject(array $project, string $presetType): array
|
||||
{
|
||||
return match ($presetType) {
|
||||
NovaCardCreatorPreset::TYPE_TYPOGRAPHY => array_intersect_key(
|
||||
(array) Arr::get($project, 'typography', []),
|
||||
array_flip(['font_preset', 'text_color', 'accent_color', 'quote_size', 'author_size', 'letter_spacing', 'line_height', 'shadow_preset', 'quote_mark_preset', 'text_panel_style', 'text_glow', 'text_stroke']),
|
||||
),
|
||||
NovaCardCreatorPreset::TYPE_LAYOUT => array_intersect_key(
|
||||
(array) Arr::get($project, 'layout', []),
|
||||
array_flip(['layout', 'position', 'alignment', 'padding', 'max_width']),
|
||||
),
|
||||
NovaCardCreatorPreset::TYPE_BACKGROUND => array_intersect_key(
|
||||
(array) Arr::get($project, 'background', []),
|
||||
array_flip(['type', 'gradient_preset', 'gradient_colors', 'solid_color', 'overlay_style', 'blur_level', 'opacity', 'brightness', 'contrast', 'texture_overlay', 'gradient_direction']),
|
||||
),
|
||||
NovaCardCreatorPreset::TYPE_STYLE => [
|
||||
'typography' => array_intersect_key(
|
||||
(array) Arr::get($project, 'typography', []),
|
||||
array_flip(['font_preset', 'text_color', 'accent_color', 'shadow_preset', 'quote_mark_preset', 'text_panel_style']),
|
||||
),
|
||||
'background' => array_intersect_key(
|
||||
(array) Arr::get($project, 'background', []),
|
||||
array_flip(['gradient_preset', 'gradient_colors', 'overlay_style']),
|
||||
),
|
||||
'effects' => (array) Arr::get($project, 'effects', []),
|
||||
],
|
||||
NovaCardCreatorPreset::TYPE_STARTER => array_intersect_key($project, array_flip([
|
||||
'template', 'layout', 'typography', 'background', 'decorations', 'effects', 'frame',
|
||||
])),
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
|
||||
private function sanitizeConfig(array $config, string $type): array
|
||||
{
|
||||
// Strip deeply nested unknowns and limit total size.
|
||||
$encoded = json_encode($config);
|
||||
if ($encoded === false || mb_strlen($encoded) > 32_768) {
|
||||
abort(422, 'Preset config is too large.');
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function authorizeOwner(User $user, NovaCardCreatorPreset $preset): void
|
||||
{
|
||||
if ((int) $preset->user_id !== (int) $user->id) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(NovaCardCreatorPreset $preset): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $preset->id,
|
||||
'name' => (string) $preset->name,
|
||||
'preset_type' => (string) $preset->preset_type,
|
||||
'config_json' => $preset->config_json ?? [],
|
||||
'is_default' => (bool) $preset->is_default,
|
||||
'created_at' => optional($preset->created_at)?->toISOString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user