- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
151 lines
5.4 KiB
PHP
151 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Domain\SmartLink\Repositories;
|
|
|
|
use App\Domain\SmartLink\Entities\SmartLink;
|
|
use App\Domain\SmartLink\Enums\LinkStatus;
|
|
use App\Domain\SmartLink\Enums\LinkType;
|
|
use App\Domain\SmartLink\ValueObjects\LinkSettings;
|
|
use App\Domain\SmartLink\ValueObjects\LinkTitle;
|
|
use App\Domain\SmartLink\ValueObjects\ShortCode;
|
|
use App\Domain\SmartLink\ValueObjects\SmartLinkId;
|
|
use App\Framework\Core\ValueObjects\Timestamp;
|
|
use App\Framework\Database\ConnectionInterface;
|
|
use App\Framework\Database\ValueObjects\SqlQuery;
|
|
|
|
final readonly class DatabaseSmartLinkRepository implements SmartLinkRepository
|
|
{
|
|
public function __construct(
|
|
private ConnectionInterface $connection
|
|
) {
|
|
}
|
|
|
|
public function save(SmartLink $link): void
|
|
{
|
|
$sql = 'INSERT INTO smart_links (id, short_code, type, title, cover_image_url, status, user_id, settings, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
type = VALUES(type),
|
|
title = VALUES(title),
|
|
cover_image_url = VALUES(cover_image_url),
|
|
status = VALUES(status),
|
|
settings = VALUES(settings),
|
|
updated_at = VALUES(updated_at)';
|
|
|
|
$query = SqlQuery::create($sql, [
|
|
$link->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']))
|
|
);
|
|
}
|
|
}
|