diff --git a/app/Domains/Link/Actions/CreateLink.php b/app/Domains/Link/Actions/CreateLink.php new file mode 100644 index 0000000..fdf09b4 --- /dev/null +++ b/app/Domains/Link/Actions/CreateLink.php @@ -0,0 +1,53 @@ +generateSlug($workspace->id, $data['domain_id'] ?? null); + + $link = Link::create([ + 'workspace_id' => $workspace->id, + 'domain_id' => $data['domain_id'] ?? null, + 'slug' => $slug, + 'target_url' => $data['target_url'], + 'title' => $data['title'] ?? null, + 'description' => $data['description'] ?? null, + 'status' => 'active', + 'expires_at' => $data['expires_at'] ?? null, + 'click_limit' => $data['click_limit'] ?? null, + 'rules' => $data['rules'] ?? null, + 'pixel_config' => $data['pixel_config'] ?? null, + 'tags' => $data['tags'] ?? null, + 'created_by' => $creator->id, + 'updated_by' => $creator->id, + ]); + + $this->cache->warmFromLink($link); + + return $link; + } + + private function generateSlug(int $workspaceId, ?int $domainId, int $length = 6): string + { + do { + $slug = Str::random($length); + $exists = Link::where('slug', $slug) + ->where('workspace_id', $workspaceId) + ->where('domain_id', $domainId) + ->exists(); + } while ($exists); + + return $slug; + } +} diff --git a/app/Domains/Link/Actions/DeleteLink.php b/app/Domains/Link/Actions/DeleteLink.php new file mode 100644 index 0000000..7e98eb4 --- /dev/null +++ b/app/Domains/Link/Actions/DeleteLink.php @@ -0,0 +1,17 @@ +cache->forget($link->slug, $link->domain_id); + $link->delete(); + } +} diff --git a/app/Domains/Link/Actions/UpdateLink.php b/app/Domains/Link/Actions/UpdateLink.php new file mode 100644 index 0000000..58bf9a5 --- /dev/null +++ b/app/Domains/Link/Actions/UpdateLink.php @@ -0,0 +1,33 @@ +update([ + 'target_url' => $data['target_url'] ?? $link->target_url, + 'title' => $data['title'] ?? $link->title, + 'description' => $data['description'] ?? $link->description, + 'status' => $data['status'] ?? $link->status, + 'expires_at' => $data['expires_at'] ?? $link->expires_at, + 'click_limit' => $data['click_limit'] ?? $link->click_limit, + 'rules' => $data['rules'] ?? $link->rules, + 'pixel_config' => $data['pixel_config'] ?? $link->pixel_config, + 'tags' => $data['tags'] ?? $link->tags, + 'updated_by' => $updater->id, + ]); + + $this->cache->forget($link->slug, $link->domain_id); + $this->cache->warmFromLink($link->fresh()); + + return $link->fresh(); + } +} diff --git a/app/Domains/Link/Services/LinkCacheService.php b/app/Domains/Link/Services/LinkCacheService.php new file mode 100644 index 0000000..2e71507 --- /dev/null +++ b/app/Domains/Link/Services/LinkCacheService.php @@ -0,0 +1,50 @@ +prefix}{$domainId}:{$slug}" + : "{$this->prefix}{$slug}"; + } + + public function put(string $slug, array $data, ?int $domainId = null): void + { + $key = $this->key($slug, $domainId); + Redis::hMSet($key, $data); + Redis::expire($key, 86400); + } + + public function get(string $slug, ?int $domainId = null): ?array + { + $key = $this->key($slug, $domainId); + $data = Redis::hGetAll($key); + return empty($data) ? null : $data; + } + + public function forget(string $slug, ?int $domainId = null): void + { + Redis::del($this->key($slug, $domainId)); + } + + public function warmFromLink(Link $link): void + { + $this->put($link->slug, [ + 'target' => $link->target_url, + 'status' => $link->status, + 'rules_json' => $link->rules ? json_encode($link->rules) : '', + 'expires_at' => $link->expires_at?->timestamp ?? '', + 'workspace_id' => (string) $link->workspace_id, + 'pixel_config' => $link->pixel_config ? json_encode($link->pixel_config) : '', + 'link_id' => (string) $link->id, + ], $link->domain_id); + } +} diff --git a/tests/Unit/Link/LinkCacheServiceTest.php b/tests/Unit/Link/LinkCacheServiceTest.php new file mode 100644 index 0000000..337427d --- /dev/null +++ b/tests/Unit/Link/LinkCacheServiceTest.php @@ -0,0 +1,27 @@ +put('testslug', [ + 'target' => 'https://example.com', + 'status' => 'active', + 'workspace_id' => 1, + ]); + + $data = $service->get('testslug'); + + expect($data['target'])->toBe('https://example.com') + ->and($data['status'])->toBe('active'); +}); + +it('returns null for unknown slug', function () { + $service = new LinkCacheService; + expect($service->get('nonexistent-' . uniqid()))->toBeNull(); +});