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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -50,6 +50,6 @@ final readonly class ChannelResult
public function isFailure(): bool
{
return !$this->success;
return ! $this->success;
}
}

View File

@@ -4,12 +4,12 @@ declare(strict_types=1);
namespace App\Framework\Notification;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\ValueObjects\NotificationId;
use App\Framework\Notification\ValueObjects\NotificationPriority;
use App\Framework\Notification\ValueObjects\NotificationStatus;
use App\Framework\Notification\ValueObjects\NotificationType;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Core notification entity
@@ -258,7 +258,7 @@ final readonly class Notification
'title' => $this->title,
'body' => $this->body,
'data' => $this->data,
'channels' => array_map(fn($c) => $c->value, $this->channels),
'channels' => array_map(fn ($c) => $c->value, $this->channels),
'priority' => $this->priority->value,
'status' => $this->status->value,
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),

View File

@@ -49,14 +49,16 @@ final readonly class NotificationDispatcher
channel: $channelType,
errorMessage: "Channel not configured: {$channelType->value}"
);
continue;
}
if (!$channel->supports($notification)) {
if (! $channel->supports($notification)) {
$results[] = \App\Framework\Notification\Channels\ChannelResult::failure(
channel: $channelType,
errorMessage: "Channel does not support this notification"
);
continue;
}
@@ -108,6 +110,7 @@ final readonly class NotificationDispatcher
{
if ($async) {
$this->sendLater($notification);
return null;
}

View File

@@ -44,7 +44,7 @@ final readonly class NotificationResult
*/
public function isFailure(): bool
{
return !$this->isSuccess();
return ! $this->isSuccess();
}
/**
@@ -56,7 +56,7 @@ final readonly class NotificationResult
{
return array_filter(
$this->channelResults,
fn(ChannelResult $result) => $result->isSuccess()
fn (ChannelResult $result) => $result->isSuccess()
);
}
@@ -69,7 +69,7 @@ final readonly class NotificationResult
{
return array_filter(
$this->channelResults,
fn(ChannelResult $result) => $result->isFailure()
fn (ChannelResult $result) => $result->isFailure()
);
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Notification\Storage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\SqlQuery;
use App\Framework\Notification\Notification;
@@ -12,7 +13,6 @@ use App\Framework\Notification\ValueObjects\NotificationId;
use App\Framework\Notification\ValueObjects\NotificationPriority;
use App\Framework\Notification\ValueObjects\NotificationStatus;
use App\Framework\Notification\ValueObjects\NotificationType;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Database implementation of NotificationRepository
@@ -45,7 +45,7 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
$notification->title,
$notification->body,
json_encode($notification->data),
json_encode(array_map(fn($c) => $c->value, $notification->channels)),
json_encode(array_map(fn ($c) => $c->value, $notification->channels)),
$notification->priority->value,
$notification->status->value,
$notification->createdAt->format('Y-m-d H:i:s'),
@@ -85,7 +85,7 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
$rows = $this->connection->query($query)->fetchAll();
return array_map(fn($row) => $this->hydrateNotification($row), $rows);
return array_map(fn ($row) => $this->hydrateNotification($row), $rows);
}
public function findUnreadByUser(string $userId, int $limit = 20): array
@@ -103,7 +103,7 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
$rows = $this->connection->query($query)->fetchAll();
return array_map(fn($row) => $this->hydrateNotification($row), $rows);
return array_map(fn ($row) => $this->hydrateNotification($row), $rows);
}
public function countUnreadByUser(string $userId): int
@@ -190,7 +190,7 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
private function hydrateNotification(array $row): Notification
{
$channels = array_map(
fn($c) => NotificationChannel::from($c),
fn ($c) => NotificationChannel::from($c),
json_decode($row['channels'], true)
);

View File

@@ -4,10 +4,9 @@ declare(strict_types=1);
namespace App\Framework\Notification\ValueObjects;
use App\Framework\DateTime\SystemClock;
use App\Framework\Ulid\UlidGenerator;
use App\Framework\Ulid\UlidValidator;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\SystemClock;
/**
* Unique identifier for notifications
@@ -25,15 +24,17 @@ final readonly class NotificationId
{
$clock = new SystemClock();
$generator = new UlidGenerator();
return new self($generator->generate($clock));
}
public static function fromString(string $id): self
{
$validator = new UlidValidator();
if (!$validator->isValid($id)) {
if (! $validator->isValid($id)) {
throw new \InvalidArgumentException("Invalid notification ID: {$id}");
}
return new self($id);
}