id->toString(), $link->shortCode->toString(), $link->type->value, $link->title->toString(), $link->coverImageUrl, $link->status->value, $link->userId, json_encode($link->settings->toArray()), $link->createdAt->format('Y-m-d H:i:s'), $link->updatedAt->format('Y-m-d H:i:s'), ]); $this->connection->execute($query); } public function findById(SmartLinkId $id): ?SmartLink { $sql = 'SELECT * FROM smart_links WHERE id = ?'; $query = SqlQuery::create($sql, [$id->toString()]); $result = $this->connection->query($query); $row = $result->fetch(); return $row ? $this->hydrate($row) : null; } public function findByShortCode(ShortCode $shortCode): ?SmartLink { $sql = 'SELECT * FROM smart_links WHERE short_code = ?'; $query = SqlQuery::create($sql, [$shortCode->toString()]); $result = $this->connection->query($query); $row = $result->fetch(); return $row ? $this->hydrate($row) : null; } public function findByUserId(string $userId, ?LinkStatus $status = null): array { if ($status !== null) { $sql = 'SELECT * FROM smart_links WHERE user_id = ? AND status = ? ORDER BY created_at DESC'; $query = SqlQuery::create($sql, [$userId, $status->value]); } else { $sql = 'SELECT * FROM smart_links WHERE user_id = ? ORDER BY created_at DESC'; $query = SqlQuery::create($sql, [$userId]); } $result = $this->connection->query($query); $rows = $result->fetchAll(); return array_map(fn (array $row) => $this->hydrate($row), $rows); } public function existsShortCode(ShortCode $shortCode): bool { $sql = 'SELECT COUNT(*) as count FROM smart_links WHERE short_code = ?'; $query = SqlQuery::create($sql, [$shortCode->toString()]); $result = $this->connection->query($query); $count = $result->fetch(); return ($count['count'] ?? 0) > 0; } public function delete(SmartLinkId $id): void { $sql = 'DELETE FROM smart_links WHERE id = ?'; $query = SqlQuery::create($sql, [$id->toString()]); $this->connection->execute($query); } public function findActiveLinks(): array { $sql = 'SELECT * FROM smart_links WHERE status = ? ORDER BY created_at DESC'; $query = SqlQuery::create($sql, [LinkStatus::ACTIVE->value]); $result = $this->connection->query($query); $rows = $result->fetchAll(); return array_map(fn (array $row) => $this->hydrate($row), $rows); } public function getTotalClicks(SmartLinkId $id): int { $sql = 'SELECT COUNT(*) as count FROM click_events WHERE link_id = ?'; $query = SqlQuery::create($sql, [$id->toString()]); $result = $this->connection->query($query); $row = $result->fetch(); return (int) ($row['count'] ?? 0); } private function hydrate(array $row): SmartLink { $settings = json_decode($row['settings'], true); return new SmartLink( id: SmartLinkId::fromString($row['id']), shortCode: ShortCode::fromString($row['short_code']), type: LinkType::from($row['type']), title: LinkTitle::fromString($row['title']), coverImageUrl: $row['cover_image_url'], status: LinkStatus::from($row['status']), userId: $row['user_id'], settings: new LinkSettings( trackClicks: (bool) $settings['track_clicks'], enableGeoRouting: (bool) $settings['enable_geo_routing'], showPreview: (bool) $settings['show_preview'], customDomain: $settings['custom_domain'] ?? null, clickLimit: $settings['click_limit'] ?? null, password: $settings['password'] ?? null ), createdAt: Timestamp::fromDateTime(new \DateTimeImmutable($row['created_at'])), updatedAt: Timestamp::fromDateTime(new \DateTimeImmutable($row['updated_at'])) ); } }