Files
michaelschiemer/src/Domain/SmartLink/Repositories/DatabaseSmartLinkRepository.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- 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.
2025-10-25 19:18:37 +02:00

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']))
);
}
}