docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
namespace App\Framework\Notification;
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
*
* Immutable value object representing a notification
*/
final readonly class Notification
{
/**
* @param NotificationId $id Unique notification identifier
* @param string $recipientId User/Entity receiving the notification
* @param NotificationType $type Notification category
* @param string $title Notification title
* @param string $body Notification message body
* @param Timestamp $createdAt Creation timestamp
* @param array<string, mixed> $data Additional structured data
* @param array<NotificationChannel> $channels Delivery channels
* @param NotificationPriority $priority Delivery priority
* @param NotificationStatus $status Current status
* @param Timestamp|null $sentAt Delivery timestamp
* @param Timestamp|null $readAt Read timestamp
* @param string|null $actionUrl Optional action URL
* @param string|null $actionLabel Optional action button label
*/
public function __construct(
public NotificationId $id,
public string $recipientId,
public NotificationType $type,
public string $title,
public string $body,
public Timestamp $createdAt,
public array $data = [],
public array $channels = [],
public NotificationPriority $priority = NotificationPriority::NORMAL,
public NotificationStatus $status = NotificationStatus::PENDING,
public ?Timestamp $sentAt = null,
public ?Timestamp $readAt = null,
public ?string $actionUrl = null,
public ?string $actionLabel = null
) {
if (empty($recipientId)) {
throw new \InvalidArgumentException('Recipient ID cannot be empty');
}
if (empty($title)) {
throw new \InvalidArgumentException('Title cannot be empty');
}
if (empty($body)) {
throw new \InvalidArgumentException('Body cannot be empty');
}
if (empty($channels)) {
throw new \InvalidArgumentException('At least one delivery channel is required');
}
}
public static function create(
string $recipientId,
NotificationType $type,
string $title,
string $body,
NotificationChannel ...$channels
): self {
return new self(
id: NotificationId::generate(),
recipientId: $recipientId,
type: $type,
title: $title,
body: $body,
createdAt: Timestamp::now(),
data: [],
channels: $channels,
priority: NotificationPriority::NORMAL,
status: NotificationStatus::PENDING
);
}
public function withData(array $data): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: [...$this->data, ...$data],
channels: $this->channels,
priority: $this->priority,
status: $this->status,
sentAt: $this->sentAt,
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function withPriority(NotificationPriority $priority): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $priority,
status: $this->status,
sentAt: $this->sentAt,
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function withAction(string $url, string $label): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: $this->status,
sentAt: $this->sentAt,
readAt: $this->readAt,
actionUrl: $url,
actionLabel: $label
);
}
public function markAsSent(): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: NotificationStatus::SENT,
sentAt: Timestamp::now(),
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function markAsDelivered(): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: NotificationStatus::DELIVERED,
sentAt: $this->sentAt ?? Timestamp::now(),
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function markAsRead(): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: NotificationStatus::READ,
sentAt: $this->sentAt,
readAt: Timestamp::now(),
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function markAsFailed(): self
{
return new self(
id: $this->id,
recipientId: $this->recipientId,
type: $this->type,
title: $this->title,
body: $this->body,
createdAt: $this->createdAt,
data: $this->data,
channels: $this->channels,
priority: $this->priority,
status: NotificationStatus::FAILED,
sentAt: $this->sentAt,
readAt: $this->readAt,
actionUrl: $this->actionUrl,
actionLabel: $this->actionLabel
);
}
public function isRead(): bool
{
return $this->status === NotificationStatus::READ;
}
public function hasAction(): bool
{
return $this->actionUrl !== null;
}
public function supportsChannel(NotificationChannel $channel): bool
{
foreach ($this->channels as $supportedChannel) {
if ($supportedChannel === $channel) {
return true;
}
}
return false;
}
public function toArray(): array
{
return [
'id' => $this->id->toString(),
'recipient_id' => $this->recipientId,
'type' => $this->type->toString(),
'title' => $this->title,
'body' => $this->body,
'data' => $this->data,
'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'),
'sent_at' => $this->sentAt?->format('Y-m-d H:i:s'),
'read_at' => $this->readAt?->format('Y-m-d H:i:s'),
'action_url' => $this->actionUrl,
'action_label' => $this->actionLabel,
];
}
}